Introduction
Python’s asterisk operator (*
) is deceptively powerful. While beginners often encounter it first in the context of multiplication, it plays a much broader and more elegant role when unpacking iterables, collecting function arguments, and working with dynamic data structures.
Table of contents
- Introduction
- What Does "Unpacking" Mean?
- Basic List/Tuple Unpacking with *
- Basic Dictionary Unpacking with **
- Using *args and **kwargs in Functions
- Unpacking in Function Calls
- Merging and Copying Iterables with *
- Unpacking in Loops
- Unpacking Nested Structures
- Common Pitfalls
- Summary Table
- Final Thoughts
In this article, we’ll explore how *
and its double-star sibling **
are used to unpack and repack data in Python.
What Does "Unpacking" Mean?
Unpacking refers to extracting values from an iterable (like a list, tuple, set, or string) and assigning them to individual variables. It’s Python’s way of saying: “break this collection into parts and distribute them.”
There are two key types of unpacking:
- Left-hand unpacking: Distribute elements into variables (
a, *rest = iterable
) - Function argument unpacking: Pass elements as arguments using
*
or**
Basic List/Tuple Unpacking with *
Python allows you to unpack values directly into variables, and the *
operator makes this more flexible by collecting the "rest" of the items into a list.
1 2 3 4 | a, b, *rest = [1, 2, 3, 4, 5] print(a) # 1 print(b) # 2 print(rest) # [3, 4, 5] |
You can place the *
at different positions:
1 2 3 4 5 6 | *start, end = [10, 20, 30, 40] print(start) # [10, 20, 30] print(end) # 40 first, *middle, last = [1, 2, 3, 4, 5] print(middle) # [2, 3, 4] |
⚠️ Only one starred target is allowed in any unpacking expression.
Basic Dictionary Unpacking with **
Python allows you to use the double asterisk **
to unpack dictionaries—a powerful feature for merging dictionaries or passing their contents into functions as named arguments.
Basic Example
1 2 3 4 | dict1 = {"a": 1, "b": 2} dict2 = {"c": 3} merged_dict = {**dict1, **dict2} print(merged_dict) |
Output:
{'a': 1, 'b': 2, 'c': 3}
In this example:
**dict1
unpacks the contents ofdict1
into key-value pairs.**dict2
unpacksdict2
as well.- The result is a new dictionary with keys and values from both.
This is a clean and Pythonic way to combine dictionaries introduced in Python 3.5+.
Key Overwrite Behavior
If both dictionaries contain the same key, the last one wins—just like dict.update()
.
1 2 3 4 | dict1 = {"a": 1, "b": 2} dict2 = {"b": 99, "c": 3} merged = {**dict1, **dict2} print(merged) |
Output:
1 | {'a': 1, 'b': 99, 'c': 3} |
Here, the value of 'b'
from dict2
overwrites the one from dict1
.
Merging Multiple Dictionaries
You can merge more than two dictionaries at once:
1 2 3 4 5 | d1 = {"x": 1} d2 = {"y": 2} d3 = {"z": 3} combined = {**d1, **d2, **d3} print(combined) |
Output:
1 | {'x': 1, 'y': 2, 'z': 3} |
This works with any number of unpacked dictionaries.
Using with Dictionary Literals
You can even mix in unpacked dictionaries with new key-value pairs:
1 2 3 | defaults = {"theme": "light", "font": "Arial"} settings = {**defaults, "font": "Helvetica", "size": 12} print(settings) |
Output:
1 | {'theme': 'light', 'font': 'Helvetica', 'size': 12} |
Note how "font"
is overwritten, and "size"
is added.
Edge Cases and Validation
Unpacking with **
requires that all unpacked objects be dictionaries (or dict-like). If not, you’ll get a TypeError
:
1 2 3 | bad = [("a", 1), ("b", 2)] # ❌ TypeError: 'list' object is not a mapping # combined = {**bad} |
To fix this, you must explicitly convert such structures:
1 2 | valid = dict(bad) # {'a': 1, 'b': 2} combined = {**valid} |
Comparison with update()
{**d1, **d2}
creates a new dictionary.d1.update(d2)
modifiesd1
in-place and returnsNone
.
Code
1 2 3 4 5 6 7 | d1 = {"a": 1} d2 = {"b": 2} # Non-destructive merged = {**d1, **d2} # In-place update (no return) d1.update(d2) |
This makes **
unpacking especially useful when you want immutability or are composing new configurations or payloads from several sources.
Using *args
and **kwargs
in Functions
Python functions can accept a variable number of arguments using:
*args
: for positional arguments (packed as a tuple)**kwargs
: for keyword arguments (packed as a dictionary)
Example: *args
1 2 3 4 5 | def greet(*names): for name in names: print(f"Hello, {name}!") greet("Alice", "Bob", "Charlie") |
Output:
1 2 3 | Hello, Alice! Hello, Bob! Hello, Charlie! |
Example: **kwargs
1 2 3 4 5 | def show_info(**info): for key, value in info.items(): print(f"{key}: {value}") show_info(name="Dana", age=30, role="Engineer") |
Output:
1 2 3 | name: Dana age: 30 role: Engineer |
Unpacking in Function Calls
If you already have arguments in an iterable or dictionary, you can unpack them into a function using *
and **
.
1 2 3 4 5 | def add(a, b, c): return a + b + c nums = [1, 2, 3] print(add(*nums)) # 6 |
For dictionaries, use **
to match named parameters:
1 2 3 4 5 | def introduce(name, age): print(f"{name} is {age} years old.") info = {'name': 'Dana', 'age': 30} introduce(**info) |
Merging and Copying Iterables with *
The *
operator can be used to merge or shallow-copy lists and tuples.
Merging lists
1 2 3 4 | list1 = [1, 2] list2 = [3, 4] merged = [*list1, *list2] print(merged) # [1, 2, 3, 4] |
Shallow copying
1 | copy_list = [*list1] |
Merging dictionaries (with **
)
1 2 3 4 | dict1 = {'a': 1} dict2 = {'b': 2} merged_dict = {**dict1, **dict2} print(merged_dict) # {'a': 1, 'b': 2} |
Unpacking in Loops
When working with structured data like tuples of different sizes, *
allows you to capture variable-length parts:
1 2 3 4 5 6 7 | data = [(1, 2, 3), (4, 5, 6)] for a, *middle, c in data: print(middle) # Output: # [2] # [5] |
Here, *middle
grabs whatever is in between a
and c
.
Unpacking Nested Structures
Unpacking works with nested data structures too. You can even combine *
with normal tuple unpacking:
1 2 3 4 5 6 7 | nested = [(1, (2, 3)), (4, (5, 6))] for x, (y, z) in nested: print(x, y, z) # Output: # 1 2 3 # 4 5 6 |
If the inner tuple has variable length, use *
:
1 2 3 4 5 6 7 | nested = [(1, (2, 3, 4)), (5, (6, 7))] for x, (y, *rest) in nested: print(f"x={x}, y={y}, rest={rest}") # Output: # x=1, y=2, rest=[3, 4] # x=5, y=6, rest=[7] |
Common Pitfalls
-
You can only use one starred expression on the left-hand side of an unpacking assignment:
1 2
# ❌ Invalid # a, *b, *c = [1, 2, 3]
-
*
and**
only work with iterables. Non-iterable objects will raise aTypeError
. -
When unpacking into functions, ensure the number of items matches the function’s signature—unless using
*args
or**kwargs
.
Summary Table
Use Case | Syntax Example |
---|---|
Assign extra items | a, *b = [1, 2, 3] |
Function with flexible args | def f(*args): |
Function with flexible keywords | def f(**kwargs): |
Unpack list to function | f(*[1, 2, 3]) |
Unpack dict to function | f(**{'x': 1, 'y': 2}) |
Merge lists | [ *a, *b ] |
Merge dicts | { **d1, **d2 } |
Loop unpacking with rest | for a, *b, c in data: |
Final Thoughts
The *
and **
operators are essential tools for writing expressive and maintainable Python code. They simplify argument handling, data unpacking, merging structures, and working with complex or variable-length data.
Understanding how and when to use them will make your code not just shorter—but cleaner, clearer, and more Pythonic.