PEP 448: Additional Unpacking Generalizations
TL;DR
PEP 448 extended Python’s unpacking operators (* and **) in Python 3.5, allowing multiple unpackings in function calls and enabling unpacking directly within list, tuple, set, and dictionary literals. This eliminates verbose workarounds and makes code more concise and readable.
Interesting!
Before PEP 448, you couldn’t unpack multiple iterables in a single list literal. Creating [*a, *b, *c] required awkward workarounds like list(itertools.chain(a, b, c)) or manual concatenation. PEP 448 removed these arbitrary restrictions, making unpacking work naturally wherever it makes sense.
Multiple Unpacking in Function Calls
Python 3.5 allows multiple * and ** unpackings in function calls, eliminating the need for intermediate variables:
python code snippet start
# Multiple * unpackings
def greet(a, b, c, d):
print(f"{a}, {b}, {c}, and {d}")
first = [1, 2]
second = [3, 4]
greet(*first, *second) # 1, 2, 3, and 4
# Before PEP 448, this required:
combined = first + second
greet(*combined)
# Multiple ** unpackings
def configure(host, port, timeout, retries):
print(f"{host}:{port}, timeout={timeout}, retries={retries}")
defaults = {'timeout': 30, 'retries': 3}
overrides = {'port': 8080}
configure(host='localhost', **defaults, **overrides)
# localhost:8080, timeout=30, retries=3python code snippet end
The order matters: later values override earlier ones with ** unpacking.
Unpacking in Collection Literals
The most powerful feature is unpacking directly within list, tuple, set, and dictionary literals:
python code snippet start
# List unpacking
a = [1, 2, 3]
b = [4, 5]
combined = [*a, *b, 6, 7]
print(combined) # [1, 2, 3, 4, 5, 6, 7]
# Before PEP 448:
combined = a + b + [6, 7]
# Or: combined = list(itertools.chain(a, b, [6, 7]))
# Tuple unpacking
colors = ('red', 'green')
more_colors = (*colors, 'blue', 'yellow')
print(more_colors) # ('red', 'green', 'blue', 'yellow')
# Set unpacking
set1 = {1, 2, 3}
set2 = {3, 4, 5}
merged = {*set1, *set2, 6}
print(merged) # {1, 2, 3, 4, 5, 6}python code snippet end
Dictionary Merging Made Easy
Merging dictionaries becomes remarkably clean with ** unpacking:
python code snippet start
# Dictionary unpacking
base_config = {'host': 'localhost', 'port': 8000}
user_config = {'port': 9000, 'debug': True}
config = {**base_config, **user_config, 'timeout': 30}
print(config)
# {'host': 'localhost', 'port': 9000, 'debug': True, 'timeout': 30}
# Before PEP 448:
config = base_config.copy()
config.update(user_config)
config['timeout'] = 30
# Or using ChainMap (less intuitive):
from collections import ChainMap
config = dict(ChainMap({'timeout': 30}, user_config, base_config))python code snippet end
Later keys override earlier ones, so user_config['port'] wins over base_config['port'].
Practical Use Cases
Combining Data Sources
python code snippet start
# Merge multiple API responses
api_data_1 = {'users': 10, 'posts': 50}
api_data_2 = {'comments': 200, 'likes': 1500}
api_data_3 = {'shares': 75}
dashboard = {**api_data_1, **api_data_2, **api_data_3}
# {'users': 10, 'posts': 50, 'comments': 200, 'likes': 1500, 'shares': 75}
# Combine search results from multiple sources
results_db = ['result1', 'result2']
results_cache = ['result3', 'result4']
results_api = ['result5']
all_results = [*results_db, *results_cache, *results_api]python code snippet end
Building Complex Configurations
python code snippet start
# Application configuration layers
system_defaults = {'log_level': 'INFO', 'workers': 4}
environment_config = {'workers': 8}
user_overrides = {'log_level': 'DEBUG'}
# Merge with clear precedence: defaults < environment < user
final_config = {
**system_defaults,
**environment_config,
**user_overrides
}
print(final_config)
# {'log_level': 'DEBUG', 'workers': 8}python code snippet end
Flexible Function Arguments
python code snippet start
def process_items(*items, verbose=False):
if verbose:
print(f"Processing {len(items)} items")
return sum(items)
# Combine different sources
database_values = [1, 2, 3]
cache_values = [4, 5]
default_value = [0]
result = process_items(*default_value, *database_values, *cache_values, verbose=True)
# Processing 6 items
print(result) # 15python code snippet end
Flattening Nested Structures
python code snippet start
# Flatten nested lists
nested = [[1, 2], [3, 4], [5, 6]]
flat = [*nested[0], *nested[1], *nested[2]]
print(flat) # [1, 2, 3, 4, 5, 6]
# More generally:
from functools import reduce
flat = reduce(lambda acc, lst: [*acc, *lst], nested, [])
# Add headers and footers
header = ['ID', 'Name', 'Email']
data_row = ['001', 'Alice', 'alice@example.com']
footer = ['---', '---', '---']
table_row = [*header, *data_row, *footer]python code snippet end
Important Limitations
Comprehensions Not Supported
Unpacking operators don’t work inside comprehensions:
python code snippet start
# This does NOT work:
# result = [*x for x in lists] # SyntaxError
# Instead, use explicit iteration:
result = [item for sublist in lists for item in sublist]
# Or use unpacking outside the comprehension:
result = [*itertools.chain.from_iterable(lists)]python code snippet end
Dictionary Key Conflicts
When unpacking dictionaries, duplicate keys follow last-wins semantics:
python code snippet start
dict1 = {'x': 1, 'y': 2}
dict2 = {'y': 3, 'z': 4}
merged = {**dict1, **dict2}
print(merged) # {'x': 1, 'y': 3, 'z': 4} - dict2's 'y' wins
# Function calls with ** raise TypeError on duplicates
def func(x, y):
return x + y
# This raises TypeError at runtime:
# func(**{'x': 1}, **{'x': 2}) # TypeError: multiple values for 'x'python code snippet end
Migration from Old Code
Refactoring to use PEP 448 improves readability:
python code snippet start
# Before PEP 448
def build_request(base_headers, auth_headers, custom_headers):
headers = {}
headers.update(base_headers)
headers.update(auth_headers)
headers.update(custom_headers)
return headers
# After PEP 448
def build_request(base_headers, auth_headers, custom_headers):
return {**base_headers, **auth_headers, **custom_headers}
# Before PEP 448
def combine_results(results1, results2, extra_items):
return list(itertools.chain(results1, results2, extra_items))
# After PEP 448
def combine_results(results1, results2, extra_items):
return [*results1, *results2, *extra_items]python code snippet end
PEP 448 removed arbitrary restrictions on unpacking, making Python’s unpacking syntax more consistent and powerful across all contexts where it makes sense. For another concise syntax that transforms how you create collections, see dictionary comprehensions .