Function Argument Binding#
Parameter Collection#
In a function definition, * and ** collect additional arguments:
def process(*args, **kwargs):
...
args is a tuple containing additional positional arguments.
kwargs is a dictionary containing additional keyword arguments.
The names args and kwargs are conventions only. The * and **
syntax determines the behaviour.
Call-Site Unpacking#
At a call site, the same symbols perform unpacking:
process(*positional, **keywords)
*positional expands an iterable into positional arguments.
**keywords expands a mapping into keyword arguments. Its keys must be
strings because they become keyword argument names.
For example:
def submit_order(symbol, quantity):
...
values = ("AAPL", 100)
submit_order(*values)
is equivalent to:
submit_order("AAPL", 100)
Keyword unpacking:
parameters = {
"symbol": "AAPL",
"quantity": 100,
}
submit_order(**parameters)
is equivalent to:
submit_order(
symbol="AAPL",
quantity=100,
)
Unpacking a Mapping#
Using * on a dictionary iterates over its keys:
parameters = {
"symbol": "AAPL",
"quantity": 100,
}
submit_order(*parameters)
This passes:
submit_order("symbol", "quantity")
It does not pass the dictionary values.
Use **parameters when the mapping represents keyword arguments.
Argument Forwarding#
A common forwarding pattern is:
def wrapper(*args, **kwargs):
return target(*args, **kwargs)
The definition collects arguments into args and kwargs. The call then
expands them again when invoking target.
Collection and unpacking therefore occur at different stages:
caller
|
| target(*args, **kwargs)
v
argument unpacking
|
v
parameter binding
|
v
def target(*args, **kwargs)
|
+-- collect excess positional arguments into a tuple
+-- collect excess keyword arguments into a dictionary
Binding Errors#
Argument binding raises TypeError when the supplied arguments cannot be
matched to the function parameters.
Common cases include:
too many positional arguments;
a required argument is missing;
an unexpected keyword argument is supplied;
the same parameter receives multiple values.
For example:
def f(quantity):
...
f(100, quantity=200)
raises TypeError because quantity receives both a positional and a
keyword value.
Interview Notes#
Distinguish definition-side collection from call-side unpacking:
def f(*args, **kwargs):
...
f(*args, **kwargs)
In the definition:
*argscollects positional arguments into a tuple;**kwargscollects keyword arguments into a dictionary.
In the call:
*argsexpands an iterable into positional arguments;**kwargsexpands a mapping into keyword arguments.
Given a dictionary d, f(*d) passes its keys positionally, while
f(**d) treats its keys as keyword argument names.