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

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

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

Операторы * и ** при вызове функции

В присваивании * собирал остаток в список. При вызове функции тот же символ работает в обратную сторону: берёт уже готовый список или кортеж и разворачивает его в отдельные позиционные аргументы.

def add(a, b, c):
    return a + b + c

numbers = [10, 20, 30]
result = add(*numbers)
print(result)  # 60

Пython видит *numbers и ведёт себя так, как если бы вы написали add(10, 20, 30) вручную. Это удобно, когда аргументы уже лежат в коллекции и переписывать вызов поэлементно незачем.

Работает с любым итерируемым: списком, кортежем, результатом range, генератором. Можно даже комбинировать несколько *-распаковок в одном вызове:

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

print здесь получает четыре отдельных позиционных аргумента, а не два списка.

Оператор со словарём

** делает то же самое, но для именованных аргументов. Словарь разворачивается в пары имя=значение:

def greet(name, greeting):
    print(f"{greeting}, {name}!")

params = {"name": "Alice", "greeting": "Hello"}
greet(**params)  # Hello, Alice!

Порядок ключей в словаре не важен — Python сопоставляет их с параметрами функции по имени, а не по позиции.

Как и *, оператор ** можно использовать несколько раз в одном вызове:

defaults = {"greeting": "Hi"}
overrides = {"name": "Bob"}
greet(**defaults, **overrides)  # Hi, Bob!

Если один и тот же ключ встречается в двух словарях при **-распаковке, Python бросает TypeError: got multiple values for keyword argument.

Ограничение на ключи при распаковке

Все ключи словаря должны быть строками. Обычный словарь с числовым ключом абсолютно легален, но при попытке распаковать его в вызов функции:

bad = {1: "value"}
def f(**kwargs): pass

f(**bad)  # TypeError: keywords must be strings

Python не умеет отобразить 1 на имя параметра — имена параметров всегда строки. Это фундаментальное требование языка, а не особенность конкретной функции.

Ещё один момент: ключи словаря должны совпадать с именами параметров функции (если функция не объявлена с **kwargs, которая поглощает всё). Лишний ключ — TypeError: got an unexpected keyword argument.

Оба оператора — * и ** — можно комбинировать в одном вызове:

def report(title, *items, sep=","):
    print(title + sep + sep.join(items))

args_list = ["item1", "item2"]
kw = {"sep": "-"}
report("Header", *args_list, **kw)  # Header-item1-item2

Здесь *args_list разворачивается в позиционные аргументы, а **kw — в именованный. Вместе они позволяют собрать вызов функции из заранее подготовленных структур данных, не переписывая его строку вручную.