Распаковка
Представьте две чашки: в левой чай, в правой кофе. Нужно поменять напитки местами. Если сразу перелить кофе в левую чашку, чай пропадёт. Без третьей, пустой чашки не обойтись.
С переменными то же самое. Двух строк a = b и b = a не хватает: после первой старое значение a потеряно, и обе переменные ссылаются на одно число. В Python обмен записывают одной строкой:
a = 1
b = 2
a, b = b, a
print(a, b) # 2 1Роль третьей чашки здесь играет кортеж. Давайте посмотрим, как он появляется и куда потом исчезает.
Как работает распаковка
Вспомните запаянную упаковку из прошлого урока. Строка a, b = b, a сначала собирает такую упаковку, а потом вскрывает её. Шагов два:
- Справа от
=стоит кортежb, a. Python вычисляет его целиком из текущих значений и получает(2, 1). - Затем распаковка раскладывает элементы кортежа по именам слева по порядку: первый — в
a, второй — вb.
Пока правая часть не вычислена полностью, ни одно имя слева не меняется. Поэтому старое значение a не теряется: оно уже лежит в упаковке, когда a получает новое.
Нажимайте «Следующий шаг» и следите за переменными. На каждой строке с распаковкой они меняются только после того, как справа готов весь кортеж.
1def divide(a, b):2 return a // b, a % b3 4 5q, r = divide(17, 5)6print(q, r)7a, b = 1, 28a, b = b, a9print(a, b)10a, b = b, a + b11print(a, b)
Справа от = вызов divide(17, 5). Имён q и r ещё нет.
Тот же порядок объясняет и возврат нескольких значений из функции:
def divide(a, b):
return a // b, a % b
q, r = divide(17, 5)
print(q, r) # 3 2
a, b = 1, 2
a, b = b, a + b
print(a, b) # 2 3Давайте разберём пример по строкам:
divide(17, 5)возвращает один объект — кортеж(3, 2).q, r = …сразу раскладывает его:qполучает3, аrполучает2.- В строке
a, b = b, a + bвыражениеa + bсчитается по старомуa, равному1. - Поэтому
bполучает3, а не4, и программа печатает2 3.
Справа может стоять любая последовательность: x, y, z = [10, 20, 30] распаковывает список, а first, second = "ok" — строку.
На собеседовании часто просят поменять местами две переменные без третьей. Ответ — a, b = b, a, и важно объяснить, почему он работает: правая часть вычисляется целиком до первого присваивания.
Здесь картинка с упаковкой немного упрощает. Для двух-трёх имён интерпретатор оптимизирует обмен и настоящий кортеж не создаёт. Но порядок тот же: всё справа вычисляется раньше, чем меняется любое имя слева.
Имя со звёздочкой
Когда элементов больше, чем имён, одно имя можно пометить звёздочкой *. Такое имя похоже на пакет, в который ссыпают все плитки, не доставшиеся другим именам:
first, *rest = [5, 6, 7, 8]
print(first, rest) # 5 [6, 7, 8]
head, *middle, tail = (1, 2, 3, 4)
print(head, middle, tail) # 1 [2, 3] 4
x, *others = [9]
print(x, others) # 9 []Что делает каждая строка:
first, *rest—firstберёт первый элемент, аrest— все остальные;head, *middle, tail— крайние элементы получают обычные имена, а середина уходит вmiddle;x, *others = [9]— лишних элементов нет, иothersполучает пустой список.
Так первый элемент отделяют от остальных, когда длина последовательности заранее неизвестна. Например, cmd, *files = "copy a.txt b.txt".split() кладёт команду в cmd, а имена файлов — в список files.
Имя со звёздочкой всегда получает список, даже если справа кортеж. Звёздочка может стоять у первого, среднего или последнего имени, но только у одного. При двух звёздочках Python не знает, как поделить элементы между ними, и сообщает об ошибке SyntaxError.
Частая ошибка
Число имён слева не совпало с числом элементов справа:
point = (3, 4, 5)
x, y = point # ✗ValueError: too many values to unpack (expected 2)Без звёздочки имён должно быть ровно столько, сколько элементов. Число в expected 2 — это количество имён слева. Когда имён больше, сообщение другое: ValueError: not enough values to unpack (expected 3, got 2).
Исправить можно третьим именем: x, y, z = point. Если остальные элементы не нужны, подойдёт звёздочка: x, y, *rest = point.
Главное
- При распаковке Python сначала вычисляет всю правую часть, а потом раскладывает элементы по именам слева направо.
- Поэтому
a, b = b, aменяет значения местами, аreturn q, rвозвращает один кортеж. - Имя со звёздочкой собирает лишние элементы в список, возможно пустой.
- Без звёздочки число имён должно совпадать с числом элементов, иначе будет
ValueError.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.