🐍 Python - Unpacking
Unpacking helps you to write more readable code e.g. through multiple assignment. Iterator unpacking *
is also called "tuple unpacking" but it does apply to all iterables and the **
syntax even to mappings like dictionaries.
Multiple assignment. Does implicit unpacking.
x, y = 10, 20
assert x == 10
assert y == 20
You can also unpack other iterables such as lists.
x, y = [10, 20]
assert x == 10
assert y == 20
# or even strings
x, y = 'hi'
assert x == 'h'
assert y == 'i'
You can also leave the value packed.
point = 10, 20
x, y = point
assert x == 10
assert y == 20
Unpacking iterables is a great alternative to hard coded indices. Multiple assignment is very strict, it you receive more or less than you asked for, it will crash, which is generally a good thing.
# BAD
def reformat_date_bad(date_string: str) -> str:
"""Reformat MM/DD/YYYY string into YYYY-MM-DD string."""
date = date_string.split('/')
return f"{date[2]}-{date[0]}-{date[1]}"
assert reformat_date_bad('05/10/2022') == '2022-05-10'
# GOOD
def reformat_date_good(date_string: str) -> str:
month, day, year = date_string.split('/')
return f"{year}-{month}-{day}"
assert reformat_date_good('05/10/2022') == '2022-05-10'
Underscore _
signifies that you are not going to use that value.
from typing import Tuple
def get_3d_point() -> Tuple[int, int]:
return 10, 20, 30
_, _, z = get_3d_point()
assert z == 30
Placing *
before an iterable will unpack it in-place.
def show_vectors(x: int, y: int, z: int) -> str:
return f'<{x}, {y}, {z}>'
assert show_vectors(1, 2, 3) == '<1, 2, 3>'
tuple_vector = (4, 5, 6)
assert show_vectors(*tuple_vector) == '<4, 5, 6>'
list_vector = [7, 8, 9]
assert show_vectors(*list_vector) == '<7, 8, 9>'
# all iterables can be unpacked!
generator_vector = (x * x for x in range(3))
assert show_vectors(*generator_vector) == '<0, 1, 4>'
Placing **
before a mapping will unpack it in-place. This will unpack both the key and the value, which can be used as named arguments in function calls or to merge dictionaries. Using *
with dictionaries will unpack just the keys.
def show_vector(x: int, y: int, z: int) -> str:
return f'<{x}, {y}, {z}>'
dictionary_vector = {'y': 1, 'z': 2, 'x': 3}
assert show_vector(**dictionary_vector) == '<3, 1, 2>'
assert show_vector(*dictionary_vector) == '<y, z, x>'
details = {'first_name': 'Ruksi', 'last_name': 'Korpisara', 'age': 28}
update = {'age': 29, 'genre': 'male'}
updated_details = {**details, **update}
assert len(updated_details.keys()) == 4
assert updated_details['age'] == 29
You can also use *
when receiving values. This can make slicing code more readable.
from typing import List
a, b, *rest = range(5)
assert a == 0 and b == 1 and rest == [2, 3, 4]
head, *body, tail = range(5)
assert head == 0 and body == [1, 2, 3] and tail == 4
def numbers() -> List[int]:
return [1, 2, 3, 4, 5]
first, *between, last = numbers()
assert first == 1
assert between == [2, 3, 4]
assert last == 5
first, *_ = numbers()
assert first == 1
*_, last = numbers()
assert last == 5
Source
- Python Tricks The Book, Dan Bader
- Fluent Python, Luciano Ramalho
- Multiple assignment and tuple unpacking improve Python code readability