Unpacking with * and **
Spread sequences and mappings with * and ** — in calls, assignments, literals and returns.
The * and ** operators do double duty. In a function definition they collect arguments (that is *args/**kwargs). In an expression they do the opposite: they unpack a sequence or mapping into its parts.
Star unpacking in assignment
Grab the ends and keep the middle in one line — the starred name always becomes a list:
first, *middle, last = [1, 2, 3, 4, 5]
print(first) # 1
print(middle) # [2, 3, 4]
print(last) # 5Spreading into literals and calls
- Merge lists:
[*a, *b]. Merge dicts:{**a, **b}(later keys win). - Forward arguments:
func(*args, **kwargs). - Build a call from data:
point(*coords)unpacks a tuple into positional arguments.
Dict merging with {**a, **b} is the clean, non-mutating way to combine configurations — the original dictionaries are untouched.
Example
# 1) Star assignment: split head, body and tail of a log line
level, *message_parts, timestamp = 'ERROR disk is full at 09:41'.split()
print('level :', level)
print('message :', ' '.join(message_parts))
print('timestamp:', timestamp)
# 2) Layered configuration without mutating anything
defaults = {'host': 'localhost', 'port': 8000, 'debug': False}
user_cfg = {'port': 9090, 'debug': True}
config = {**defaults, **user_cfg, 'source': 'merged'}
print('config :', config)
print('defaults untouched:', defaults['port']) # still 8000
# 3) Unpack a record straight into a function call
def make_url(scheme, host, port):
return f'{scheme}://{host}:{port}'
parts = ('https', 'example.com', 443)
print(make_url(*parts))
# 4) Flatten nested data in a comprehension
rows = [[1, 2], [3, 4], [5, 6]]
flat = [x for row in rows for x in row]
print('flat :', [*flat, 7])
# Output:
# level : ERROR
# message : disk is full at
# timestamp: 09:41
# config : {'host': 'localhost', 'port': 9090, 'debug': True, 'source': 'merged'}
# defaults untouched: 8000
# https://example.com:443
# flat : [1, 2, 3, 4, 5, 6, 7]When to use it
- A function call uses **config_dict to pass a dictionary of settings as keyword arguments.
- A catch-all unpacking assigns the first and last items of a list to variables, collecting the middle with *.
- Two lists are merged inline using [*list_a, *list_b] to build a combined list without mutating either.
More examples
Extended iterable unpacking with *
Uses * to collect all middle elements into a list while binding the ends to named variables.
first, *middle, last = [1, 2, 3, 4, 5]
print(first) # 1
print(middle) # [2, 3, 4]
print(last) # 5Merge lists and dicts inline
Spreads two lists into a new list and merges two dicts, with the later dict winning on duplicate keys.
a = [1, 2, 3]
b = [4, 5, 6]
combined = [*a, *b]
print(combined) # [1, 2, 3, 4, 5, 6]
defaults = {'timeout': 30, 'retries': 3}
overrides = {'timeout': 10}
config = {**defaults, **overrides}
print(config) # {'timeout': 10, 'retries': 3}Unpack into function arguments
Unpacks a dict as keyword arguments and a tuple as positional arguments in function calls.
def connect(host, port, timeout):
print(f'Connecting to {host}:{port} (timeout={timeout}s)')
params = {'host': 'db.local', 'port': 5432, 'timeout': 10}
connect(**params)
# Connecting to db.local:5432 (timeout=10s)
args = ('cache.local', 6379, 5)
connect(*args)
# Connecting to cache.local:6379 (timeout=5s)
Discussion