*args collects an arbitrary number of positional arguments into a tuple.
**kwargs collects an arbitrary number of named arguments (key=value) into a dictionary.
This allows writing functions with a flexible interface:
def my_func(a, b, *args, **kwargs): print(a, b) print(args) print(kwargs) my_func(1, 2, 3, 4, x=10, y=20) # 1 2 # (3, 4) # {'x': 10, 'y': 20}
Nuances:
Can the order of *args and named arguments with default values be changed, for example:
def foo(a, *args, x=10, **kwargs): pass
Or must default values always be placed after *args?
Answer:
In Python 3, yes! Like this (it didn't work in Python 2):
def foo(a, *args, x=10): pass
Parameters after *args ("keyword-only arguments") can have default values or be required — they can only be specified by name.
Story
Passing incorrect arguments through *args/**kwargs
In REST API services, data marshalling automation was built through **kwargs. An extra parameter went unnoticed — the function accepted an unexpected value in kwargs, leading to loss of control logic and difficult-to-debug bugs.
Story
Duplicating named arguments
When calling a function, a developer accidentally specified a parameter both explicitly and through **kwargs, for example: my_func(a=1, **{"a": 2}). As a result — a TypeError and service crash.
Story
Forgotten positional or named-only arguments
When writing a decorator, incorrect parameter passing (order and structure were not followed) meant that the original function received an incorrect number of arguments, the agreements of which changed, and TypeError call errors began to occur in production.