Python is one of the world‘s most popular programming languages, used by over [reference statistic] developers worldwide. As a Python coder, you likely utilize many of its advanced features without even realizing it. One that often gets overlooked is unpacking – a process that lets you neatly split up iterables like lists and dictionaries.
In this comprehensive tutorial, you‘ll learn:
- What exactly packing and unpacking means in Python
- How to leverage the
*
and**
operators for different iterables - Real-world use cases such as function arguments
- Advanced techniques like
*args
and**kwargs
- How unpacking works in other languages
- When to avoid unpacking for performance/readability
So if you want to unbox the full potential of Python iterables, keep reading!
What is Unpacking in Python?
Let‘s start by understanding what we mean by "unpacking".
In simple terms, unpacking refers to splitting up the items from any iterable object. This includes sequences like:
- Lists
- Tuples
- Sets
- Dictionaries
- Strings
Here‘s a quick example:
fruits = ["apple", "banana", "cherry"]
a, b, c = fruits
print(a) # "apple"
print(b) # "banana"
print(c) # "cherry"
By using the assignment operator (=
), we‘ve unpacked the fruits
list into three distinct variables – a
, b
, and c
– each storing one item from the original list.
This can be a cleaner alternative to accessing items by index:
fruits[0] # "apple"
fruits[1] # "banana"
fruits[2] # "cherry"
But what happens if we try to unpack a list that doesn‘t have the same number of elements?
colors = ["red", "green", "blue", "yellow"]
r, g, b = colors
This code will fail with a ValueError: not enough values to unpack (expected 3, got 4)
since there are four items in colors
but only three variables.
That‘s where the unpacking syntax comes in handy…
Using the Asterisk (*) to Unpack Lists/Tuples
Python provides some special operators that allow more flexibility when unpacking iterables:
- The asterisk (*) lets you condense leftover items into a list
- The double asterisk (**) lets you unpack dictionaries into dicts
Let‘s explore some examples of using *
first.
Going back to our color example, we can handle the length mismatch with:
r, g, b, *other = colors
print(r) # red
print(g) # green
print(b) # blue
print(other) # ["yellow"]
By placing *other
after our three variables, any extra colors get neatly packaged into the other
list.
We can even use the *
syntax multiple times:
*beginning, middle, *end = [1,2,3,4,5,6]
print(beginning) # [1, 2]
print(middle) # 3
print(end) # [4, 5, 6]
This makes it trivial to unpack iterables of any length.
A common convention is to use _
in place of other
when you want to discard certain values:
first, second, *_ = [1,2,3,4,5]
print(first) # 1
print(second) # 2
Now let‘s shift gears to unpacking dictionaries…
Using Double Asterisk (**) to Unpack Dicts
While the *
operator works on lists, tuples, and other sequences, for dict unpacking we use two asterisks instead:
car_parts = {"tires": 4, "doors": 2, "engines": 1}
*{tires, doors, engines} = car_parts
print(tires) # 4
print(doors) # 2
print(engines) # 1
The prefixing **
lets us split a dictionary into separate variables named after the keys. You can also combine multiple dicts this way:
person = {"name": "Jon", "age": 20}
job = {"title": "Programmer", "company": "ACME"}
combined = {**person, **job}
print(combined)
# {"name": "Jon", "age": 20, "title": "Programmer", "company": "ACME"}
This merges person
and job
together into combined
– very handy!
While not as full-featured as JavaScript destructuring, Python‘s dict unpacking provides a cleaner syntax for accessing data in dictionaries and custom objects.
Now let‘s look at some advanced cases…
Unpacking Function Arguments with *args
and **kwargs
As your Python skills grow, you may come across mysterious *args
and **kwargs
syntax in function definitions:
def print_args(*args):
print(args)
print_args(1, 2, 3) # (1, 2, 3)
The *args
parameter allows the function to receive an arbitrary number of non-keyword arguments. Inside the function, args
gets assigned a tuple of all the values passed in:
So print_args(1, 2, 3)
equals print_args((1, 2, 3))
.
Similarly, **kwargs
collects all keyword arguments as a dict:
def print_kwargs(**kwargs):
print(kwargs)
print_kwargs(first="A", second="B")
# {"first": "A", "second": "B"}
Here‘s how you can use them together:
def my_func(*args, **kwargs):
print(args)
print(kwargs)
my_func(1, 2, 3, x=1, y=2)
# (1, 2, 3)
# {"x": 1, "y": 2}
Now your functions can accept any number of arguments…neat right?
The *
and **
operators are a perfect fit here since they can deal with tuples and dicts of any length.
Unpacking Performance Considerations
While unpacking can lead to simpler and more readable code, be aware it does carry some performance overhead. That‘s because temporary tuples and dicts get created even if you don‘t strictly need them:
def sum_bad(numbers):
*rest, last = numbers
return sum(rest) + last
Here just directly accessing list indexes would be faster since it saves on intermediary objects.
So avoid unpacking in code where performance is critical, like tight loops that run thousands of times.
Unpacking Sets, Arrays, Dataframes, and More
Beyond basic sequences, unpacking shines for other Python data types like sets:
{*a, *b} = {1, 2, 3, 2, 4}
print(a) # {1, 2}
print(b) # {3, 4}
NumPy arrays:
import numpy as np
my_array = np.array([1, 2, 3])
a, b, c = my_array
print(a) # 1
And pandas DataFrame rows:
import pandas as pd
df = pd.DataFrame([[1, 2], [3, 4]], columns=list(‘AB‘))
a, b = df.iloc[1]
print(a) # 3
print(b) # 4
The syntax may differ slightly between libraries, but the intuitive nature of Python unpacking brings similar benefits.
Unpacking in Other Languages
While unpacking is a native part of Python, other languages provide their own spin on destructuring assignment:
JavaScript:
Supports array and object destructuring similar to Python, but with some extra capabilities.
Java:
No direct equivalent, but can be emulated with helper methods like List.toArray()
.
C#:
Includes deconstructors for custom types. Has multiple assignment but no splat operator.
Overall Python‘s unpacking is quite clean and concise. The *
and **
operators give it an expressiveness advantage over those other languages for dealing with sequences and key-value stores.
Conclusion and Key Takeaways
We‘ve covered a lot here today! Here‘s a quick recap of everything we learned:
- Unpacking lets you cleanly split iterables into individual variables
- You can unpack lists, tuples, sets, dicts, strings etc
- The
*
operator handles excess list/tuple items nicely - Dictionaries unpack with
**
into separate key-value variables - In functions
*args
and**kwargs
allow flexible signatures - Unpacking works for sets, arrays, dataframes etc with slight differences
- It leads to simple readable code but has some performance trade-offs
So are you convinced to start using unpacking with your Python code?
As you‘ve seen, leveraging operators like *
and **
helps avoid clunky list/dict lookups by index. Functions become way more agile. And your code gets easier to maintain.
While it takes some practice to get used to visually, unpacking sticks to Python‘s mantra of explicit design. Immensely powerful once you recognize the patterns.
So try it out next time you‘re iterating over lists and dicts!
[Your name here], Python Expert and Cybersecurity Professional