Полезные идиомы Python для проектного кода

Распаковка, *args и **kwargs в сигнатурах и вызовах

Содержание курса

В предыдущем уроке мы работали с zip, enumerate и sorted — функциями, которые возвращают итерируемые объекты. Теперь разберём механизм, который стоит за тем, как Python вообще умеет «раскладывать» итерируемое по переменным — и как этим управлять через звёздочку.

Распаковка итерируемого в присваивании: базовый синтаксис и *rest

Когда Python видит несколько переменных слева от = и итерируемое справа, он идёт по элементам итерируемого и раскладывает их по переменным слева направо. Это называется распаковкой.

point = (10, 20, 30)
x, y, z = point
print(x, y, z)  # 10 20 30

Работает с любым итерируемым: кортежем, списком, строкой, результатом zip — чем угодно, что можно обойти циклом.

Проблема возникает, когда количество элементов заранее неизвестно или может меняться. Тут появляется звёздочный синтаксис.

first, *rest = [1, 2, 3, 4, 5]
print(first)  # 1
print(rest)   # [2, 3, 4, 5]

*rest собирает всё, что не «досталось» конкретным переменным, и упаковывает это в список — не в кортеж, именно в список. Звёздочная переменная может стоять не только в конце:

first, *middle, last = [1, 2, 3, 4, 5]
print(first)   # 1
print(middle)  # [2, 3, 4]
print(last)    # 5

Пython сам вычисляет, сколько элементов уйдёт в «звёздочную» переменную, ориентируясь на количество конкретных переменных вокруг.

Одно жёсткое ограничение: звёздочная переменная в одном присваивании может быть только одна. *a, *b = [1, 2, 3] — это SyntaxError. Python не умеет делить остаток между двумя «сборщиками».

Что происходит при несовпадении числа переменных

Если звёздочки нет, а количество переменных не совпадает с количеством элементов — получаем ValueError:

# Слишком много значений
a, b = [1, 2, 3]  # ValueError: too many values to unpack

# Слишком мало значений
a, b, c = [1, 2]  # ValueError: not enough values to unpack

Звёздочка страхует от этой ошибки, но только от одной стороны: если конкретных переменных больше, чем элементов, ValueError всё равно возникнет.

first, second, *rest = [1]  # ValueError: not enough values to unpack

Здесь Python пытается отдать по одному элементу first и second, но элемент только один — rest тут уже ничего не поможет.

Этот механизм — основа для понимания того, как * работает при вызовах функций и в их сигнатурах. Но об этом в следующих секциях.