Распаковка, *args и **kwargs в сигнатурах и вызовах
Содержание курса
Совместное использование *args, keyword-only параметров и **kwargs
Все три механизма — *args, keyword-only параметры и **kwargs — становятся по-настоящему полезными тогда, когда работают вместе. Посмотрим на конкретную функцию, которая сочетает их в одной сигнатуре.
def report(*args, sep=' ', **kwargs):
print(f"Values: {args}")
print(f"Separator: {sep!r}")
print(f"Extra options: {kwargs}")
Здесь три зоны ответственности:
*argsпоглощает все позиционные аргументы, которые передали при вызове;sep— keyword-only параметр с дефолтом' ': стоит после*args, поэтому передать его позиционно невозможно;**kwargsсобирает всё, что не попало ни вargs, ни вsep.
Проверим несколько вызовов:
report('a', 'b', 'c')
# Values: ('a', 'b', 'c')
# Separator: ' '
# Extra options: {}
report('a', 'b', sep='-', color='red', size=12)
# Values: ('a', 'b')
# Separator: '-'
# Extra options: {'color': 'red', 'size': 12}
report(sep='|')
# Values: ()
# Separator: '|'
# Extra options: {}
Во втором вызове 'a' и 'b' ушли в args, sep='-' попал точно в keyword-only параметр, а color и size — в kwargs. Никаких конфликтов: Python чётко разграничивает, куда что идёт, следуя порядку параметров в сигнатуре.
Третий вызов показывает, что все три зоны могут быть пустыми одновременно — и это нормально. args становится пустым кортежем (), kwargs — пустым словарём {}.
Как это работает, когда вызов сам строится из коллекций
Практически часто бывает так: и аргументы, и опции уже лежат в переменных. Тут * и ** на стороне вызова соединяются с *args/**kwargs на стороне определения:
positional = ('x', 'y', 'z')
options = {'color': 'blue', 'bold': True}
report(*positional, sep='/', **options)
# Values: ('x', 'y', 'z')
# Separator: '/'
# Extra options: {'color': 'blue', 'bold': True}
*positional разворачивается в три позиционных аргумента, они оседают в args. sep='/' передаётся именованно и попадает в keyword-only параметр. **options разворачивается в два именованных аргумента, которые не совпадают ни с каким явным параметром — и оба уходят в kwargs.
Передача kwargs дальше по цепочке
Один из типичных паттернов — функция-обёртка, которая принимает **kwargs и пробрасывает их в другую функцию:
def styled_report(*args, **kwargs):
# Извлекаем то, что нам нужно самим
prefix = kwargs.pop('prefix', '')
# Остаток передаём дальше
report(*args, **kwargs)
if prefix:
print(f"[{prefix}]")
styled_report('a', 'b', sep='-', prefix='INFO', color='green')
# Values: ('a', 'b')
# Separator: '-'
# Extra options: {'color': 'green'}
# [INFO]
kwargs.pop('prefix', '') забирает prefix из словаря до того, как он уйдёт дальше. Остаток {'color': 'green'} летит в report через **kwargs. Это чистый способ расширить функцию, не ломая её контракт с вызывающей стороной.
Именно такой паттерн — *args + **kwargs в __init__ — появится в следующем уроке при работе с классами, где конструктор должен принимать произвольный набор настроек и часть из них передавать родительскому классу.
