Learning Platform
Глоссарий Troubleshooting
Урок 04.02 · 20 мин
Средний
argskwargsUnpackingSplatPEP 448

*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 casePatternПочему
Forwarding в декоратор / wrapperdef 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 / proxydef adapt(self, *args, **kwargs): self._target(*args, **kwargs)type-agnostic forwarding

Когда НЕ нужно

WARNING

Не используйте *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)) — прямая параллель trait ExecutionPlan::execute(self, partition, context): trait composition forward’ит execution context вниз по физическому плану.


Ключевые выводы

  1. *args собирает positional в tuple; **kwargs собирает keyword в dict. Tuple/dict внутри — те же объекты, что в M02: tuple hashable+immutable, dict — hash table с insertion order.
  2. Unpacking при call (f(*lst, **d)) — обратная операция: разбросать коллекцию в аргументы. Forwarding-pattern в wrappers / decorators.
  3. PEP 448 extended unpacking в literals: [*a, *b], {**d1, **d2} — concatenation / merging без явного оператора.
  4. Не используйте *args/**kwargs без необходимости — это разрушает introspection и усложняет maintenance.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. После `def f(*args, **kwargs): print(type(args), type(kwargs))`, что выведет вызов `f(1, 2, x=3)`?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 6