*args, **kwargs и unpacking — PEP 448
*args собирает positional аргументы в tuple, **kwargs — keyword аргументы в dict. Reverse unpacking (f(*lst, **d)) разбрасывает коллекции обратно в аргументы. PEP 448 расширил это до literals ([*a, *b], {**d1, **d2}).
Это всё — прямые следствия M02: tuple immutable + hashable, dict — hash table с insertion order. Урок раскрывает, как они работают на уровне function call mechanics.
*args — variadic positional
*args в signature собирает все «лишние» позиционные аргументы в tuple:
def sum_all(*args):
print(type(args), args)
return sum(args)
sum_all(1, 2, 3) # <class 'tuple'> (1, 2, 3) → 6
sum_all() # <class 'tuple'> () → 0 (empty tuple)
sum_all(*[1, 2, 3]) # <class 'tuple'> (1, 2, 3) → 6 (unpacking при call)
Имя args — convention, не keyword. Можно *xs, *items, *everything — работает любое имя. Маркер — именно * перед именем.
Почему tuple? Потому что immutable + hashable, что соответствует семантике аргументов: function не должна мутировать переданные позиционные значения как коллекцию (отдельные ссылки могут указывать на mutable объекты, но сама args-tuple не мутирует). Cache locality + free-list (см. M02 урок 02 — PyTuple_MAXSAVESIZE = 20 free-list делает создание маленьких tuple дешёвым).
**kwargs — variadic keyword
**kwargs собирает все «лишние» keyword аргументы в dict:
def make_dict(**kwargs):
print(type(kwargs), kwargs)
return kwargs
make_dict(a=1, b=2) # <class 'dict'> {'a': 1, 'b': 2}
make_dict(**{'a': 1, 'b': 2}) # тот же результат через unpacking
Почему dict? Потому что named lookup + O(1) avg (см. M02 урок 03 — open addressing с perturbation probe). Insertion order гарантирован (PEP 468) — порядок аргументов в call сохраняется.
Полная signature
Сложная signature, где есть всё:
def full(a, /, b, *args, c, **kwargs):
print(a, b, args, c, kwargs)
Слева направо:
a— positional-only (до/).b— обычный (между/и*args).*args— собирает оставшиеся positional в tuple.c— keyword-only (после*args, обязан передаваться по имени).**kwargs— собирает оставшиеся keyword в dict.
Вызов:
full(1, 2, 3, 4, c=5, d=6, e=7)
# 1 2 (3, 4) 5 {'d': 6, 'e': 7}
a=1 (positional), b=2 (positional), args=(3, 4) — следующие позиционные; c=5 keyword-only; d=6, e=7 — попадают в **kwargs.
Unpacking при вызове
Операторы * и ** в call-site разбрасывают коллекцию в аргументы:
def f(a, b, c):
return a + b + c
# Unpack list/tuple в позиционные:
args = [1, 2, 3]
f(*args) # эквивалентно f(1, 2, 3) → 6
# Unpack dict в keyword:
kwargs = {'a': 1, 'b': 2, 'c': 3}
f(**kwargs) # эквивалентно f(a=1, b=2, c=3) → 6
# Смешанно:
f(1, **{'b': 2, 'c': 3}) # f(1, b=2, c=3) → 6
f(*[1, 2], **{'c': 3}) # f(1, 2, c=3) → 6
Это reverse того, что делает *args / **kwargs в signature: signature собирает, call-site разбрасывает. Forwarding-pattern — самое частое применение:
def wrapper(*args, **kwargs):
print('Calling f with', args, kwargs)
return f(*args, **kwargs) # forward всё, что получили
Unpacking в literals — PEP 448
Python 3.5+ расширил * и ** до использования внутри list / tuple / set / dict literals:
# Concatenation через unpacking
a = [1, 2]
b = [3, 4]
c = [*a, *b] # [1, 2, 3, 4] — unpacked в list literal
t = (*a, *b) # (1, 2, 3, 4) — в tuple
s = {*a, *b} # {1, 2, 3, 4} — в set (dedup как обычно)
# Dict merging
d1 = {'x': 1, 'y': 2}
d2 = {'y': 99, 'z': 3}
merged = {**d1, **d2} # {'x': 1, 'y': 99, 'z': 3} — d2 overrides d1 для 'y'
# Forwarding многих коллекций
combined = [*list1, 0, *list2, *list3] # позиции произвольны
Поведение: [*a, *b] эквивалентно list(a) + list(b), но более компактно и работает с любыми iterables (не только list).
См. PEP 448 — Additional Unpacking Generalizations.
Extended unpacking в assignment — PEP 3132
Звёздочка позволяет «поглотить» среднюю часть последовательности при unpacking:
g, *rest = [1, 2, 3, 4] # g=1, rest=[2, 3, 4]
*front, last = [1, 2, 3, 4] # front=[1, 2, 3], last=4
first, *middle, last = [1, 2, 3, 4, 5] # first=1, middle=[2, 3, 4], last=5
*rest — всегда list, даже если правая часть — tuple. Это удобно для разбора head/tail / first/last patterns без явных индексов.
Когда *args / **kwargs хороши
| Use case | Pattern | Почему |
|---|---|---|
| Forwarding в декоратор / wrapper | def wrap(*a, **kw): return f(*a, **kw) | preserves signature transparently |
Variadic API (как print) | def print_all(*items, sep=' ') | clean для 1..N inputs |
| Configurable factory | **options для forwarding в underlying constructor | гибко без перечисления |
| Adapter / proxy | def adapt(self, *args, **kwargs): self._target(*args, **kwargs) | type-agnostic forwarding |
Когда НЕ нужно
Не используйте *args / **kwargs, если signature известна. Это разрушает introspection: IDE не покажет параметры, type checkers не помогут, документация теряется.
Anti-pattern:
# Плохо — сигнатура «съедена»:
def calculate(*args, **kwargs):
x = kwargs.get('x', 0)
y = kwargs.get('y', 0)
return x + y
# Хорошо:
def calculate(*, x: int = 0, y: int = 0) -> int:
return x + y
Используйте *args/**kwargs только когда signature действительно variadic или вы forwarding’аете чужую функцию.
Cross-course context
Cross-course → DataFusion: 02/03 physical-plan — параметризация функции через
*args/**kwargsпараллельна построению PhysicalPlan’а в DataFusion: каждыйPhysicalExprпринимает наборColumnarValueвходов плюсExecutionContextс конфигом (аналог**kwargs— keyword-style). Forwarding-pattern (def wrap(*a, **kw): return f(*a, **kw)) — прямая параллель traitExecutionPlan::execute(self, partition, context): trait composition forward’ит execution context вниз по физическому плану.
Ключевые выводы
*argsсобирает positional в tuple;**kwargsсобирает keyword в dict. Tuple/dict внутри — те же объекты, что в M02: tuple hashable+immutable, dict — hash table с insertion order.- Unpacking при call (
f(*lst, **d)) — обратная операция: разбросать коллекцию в аргументы. Forwarding-pattern в wrappers / decorators. - PEP 448 extended unpacking в literals:
[*a, *b],{**d1, **d2}— concatenation / merging без явного оператора. - Не используйте
*args/**kwargsбез необходимости — это разрушает introspection и усложняет maintenance.