Распаковка, *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 — в именованный. Вместе они позволяют собрать вызов функции из заранее подготовленных структур данных, не переписывая его строку вручную.
