Lesson 10 introduced **kwargs on the def side — collect extras into a dict. The same ** works the call side too — spread a dict into keyword arguments:
def greet(name, age):
print(f"{name} is {age}")
person = {"name": "Ada", "age": 36}
greet(**person) # equivalent to greet(name="Ada", age=36)The dict's keys must match the function's parameter names. The values become the arguments.
Right. If a key doesn't match a parameter name, you get TypeError: got an unexpected keyword argument. If a parameter is missing, you get TypeError: missing required argument.
Beyond function calls — does ** work elsewhere?
Yes — for merging dicts:
defaults = {"timeout": 30, "retries": 3}
overrides = {"timeout": 60}
merged = {**defaults, **overrides}
# {'timeout': 60, 'retries': 3}Left-to-right, later keys win. Useful for config patterns: "start with defaults, override with what the caller passed."
And forwarding — passing my own kwargs to another function?
That's the most common use. A wrapper takes **kwargs and forwards them on:
def log_then_call(target, **kwargs):
print("calling with:", kwargs)
return target(**kwargs)Whatever keyword arguments the caller passes flow through to target. The wrapper doesn't need to know what they are.
**dict at the call site — spread into keyword argumentsdef connect(host, port, timeout):
...
opts = {"host": "db.local", "port": 5432, "timeout": 30}
connect(**opts)
# Same as: connect(host="db.local", port=5432, timeout=30)The dict keys become parameter names; the dict values become argument values.
**dict with other argumentsconnect("db.local", **{"port": 5432, "timeout": 30})
connect(host="db.local", **{"port": 5432, "timeout": 30})A **dict can sit alongside positional and explicit keyword arguments — but it can't conflict. connect(host="x", **{"host": "y"}) is a TypeError because both specify host.
def logged(fn):
def wrapper(*args, **kwargs):
print("called:", fn.__name__, args, kwargs)
return fn(*args, **kwargs)
return wrapperThe wrapper doesn't know or care what fn expects. It collects everything with *args, **kwargs and passes it on with the spread operators.
**dict in a dict literal — mergeSince Python 3.5+ you can spread dicts into dict construction:
a = {"x": 1, "y": 2}
b = {"y": 99, "z": 3}
merged = {**a, **b}
# {'x': 1, 'y': 99, 'z': 3} — later keys winReads left-to-right. The keys from b overwrite same-named keys from a.
| operator (Python 3.9+)merged = a | b
# {'x': 1, 'y': 99, 'z': 3}Same result, cleaner syntax. Both are common; ** is more universal because it also handles function calls.
defaults = {"verbose": False, "timeout": 30}
user_settings = {"timeout": 60}
final = {**defaults, **user_settings} # {'verbose': False, 'timeout': 60}row = {"name": "Ada", "age": 36, "city": "London"}
person = make_person(**row)
# Same as: make_person(name="Ada", age=36, city="London")This lets you use a dict as a record and unpack it into a function in one line.
base = {"a": 1, "b": 2}
plus_c = {**base, "c": 3}
# {'a': 1, 'b': 2, 'c': 3} — base unchangedA non-mutating way to extend a dict.
* and ** summary| Form | Direction | Container | Where it goes |
|---|---|---|---|
def f(*args) | def-side | tuple | extra positionals |
def f(**kwargs) | def-side | dict | extra keywords |
f(*lst) | call-side | list/tuple | spread into positionals |
f(**dct) | call-side | dict | spread into keywords |
[*a, *b] | literal | list | merge two lists |
{**a, **b} | literal | dict | merge two dicts |
Same symbols, mirrored use.
Lesson 10 introduced **kwargs on the def side — collect extras into a dict. The same ** works the call side too — spread a dict into keyword arguments:
def greet(name, age):
print(f"{name} is {age}")
person = {"name": "Ada", "age": 36}
greet(**person) # equivalent to greet(name="Ada", age=36)The dict's keys must match the function's parameter names. The values become the arguments.
Right. If a key doesn't match a parameter name, you get TypeError: got an unexpected keyword argument. If a parameter is missing, you get TypeError: missing required argument.
Beyond function calls — does ** work elsewhere?
Yes — for merging dicts:
defaults = {"timeout": 30, "retries": 3}
overrides = {"timeout": 60}
merged = {**defaults, **overrides}
# {'timeout': 60, 'retries': 3}Left-to-right, later keys win. Useful for config patterns: "start with defaults, override with what the caller passed."
And forwarding — passing my own kwargs to another function?
That's the most common use. A wrapper takes **kwargs and forwards them on:
def log_then_call(target, **kwargs):
print("calling with:", kwargs)
return target(**kwargs)Whatever keyword arguments the caller passes flow through to target. The wrapper doesn't need to know what they are.
**dict at the call site — spread into keyword argumentsdef connect(host, port, timeout):
...
opts = {"host": "db.local", "port": 5432, "timeout": 30}
connect(**opts)
# Same as: connect(host="db.local", port=5432, timeout=30)The dict keys become parameter names; the dict values become argument values.
**dict with other argumentsconnect("db.local", **{"port": 5432, "timeout": 30})
connect(host="db.local", **{"port": 5432, "timeout": 30})A **dict can sit alongside positional and explicit keyword arguments — but it can't conflict. connect(host="x", **{"host": "y"}) is a TypeError because both specify host.
def logged(fn):
def wrapper(*args, **kwargs):
print("called:", fn.__name__, args, kwargs)
return fn(*args, **kwargs)
return wrapperThe wrapper doesn't know or care what fn expects. It collects everything with *args, **kwargs and passes it on with the spread operators.
**dict in a dict literal — mergeSince Python 3.5+ you can spread dicts into dict construction:
a = {"x": 1, "y": 2}
b = {"y": 99, "z": 3}
merged = {**a, **b}
# {'x': 1, 'y': 99, 'z': 3} — later keys winReads left-to-right. The keys from b overwrite same-named keys from a.
| operator (Python 3.9+)merged = a | b
# {'x': 1, 'y': 99, 'z': 3}Same result, cleaner syntax. Both are common; ** is more universal because it also handles function calls.
defaults = {"verbose": False, "timeout": 30}
user_settings = {"timeout": 60}
final = {**defaults, **user_settings} # {'verbose': False, 'timeout': 60}row = {"name": "Ada", "age": 36, "city": "London"}
person = make_person(**row)
# Same as: make_person(name="Ada", age=36, city="London")This lets you use a dict as a record and unpack it into a function in one line.
base = {"a": 1, "b": 2}
plus_c = {**base, "c": 3}
# {'a': 1, 'b': 2, 'c': 3} — base unchangedA non-mutating way to extend a dict.
* and ** summary| Form | Direction | Container | Where it goes |
|---|---|---|---|
def f(*args) | def-side | tuple | extra positionals |
def f(**kwargs) | def-side | dict | extra keywords |
f(*lst) | call-side | list/tuple | spread into positionals |
f(**dct) | call-side | dict | spread into keywords |
[*a, *b] | literal | list | merge two lists |
{**a, **b} | literal | dict | merge two dicts |
Same symbols, mirrored use.
Create a free account to get started. Paid plans unlock all tracks.