What Is Iterable Unpacking in Python?

The featured image for the article titled,

Today, we’re expanding our new concept series by building on the concept of an iterable. Specifically, we’ll be looking at a feature of iterables called iterable unpacking. Let’s get into it!

Table of Contents

Concept Overview

In Python, iterable unpacking refers to the ability to extract the contents of an iterable into a sequence of variables.

To illustrate iterable unpacking, imagine that you have a list where the elements are in a predictable order, such as a list containing a student’s ID number as well as their first and last name. When processing this list, we might want to move each element into a variable with a reasonable name, rather than accessing the indices directly, as follows:

student = [3, "Jeremy", "Grifski"]
identifier = student[0]
first_name = student[1]
last_name = student[2]

Naturally, this is a bit cumbersome, so the Python developers came up with a much nicer syntax called iterable unpacking:

student = [3, "Jeremy", "Grifski"]
identifier, first_name, last_name = student

Later, if you decide to expand your dataset to include other information about each student, you might convince yourself that the only solution is to fall back on indexing and slicing:

student = [3, "Jeremy", "Grifski", 175, 3.2]
identifier, first_name, last_name, data = student[0], student[1], student[2], student[3:]

Fortunately, you can save yourself a lot of time by using the iterable unpacking operator, *, as follows:

student = [3, "Jeremy", "Grifski", 175, 3.2]
identifier, first_name, last_name, *data = student

In other words, aside from the first three elements in the list, everything else will be stored in a new list in data.

Next, we’ll talk about a few places where iterable unpacking is commonly used.

Common Use Cases

The very first place that iterable unpacking comes to mind for me is in the *args syntax you sometimes see as function parameter, such as in max() and min(). The whole idea being that you can pass in any number of values and they all get captured in the args variable, much like the following:

identifier, first_name, last_name, *args = 3, "Jeremy", "Grifski", 175, 3.2

Another place that this comes to mind for me is when you want a convenient way to swap variables. Normally, you have to introduce a temporary variable as follows:

first = "Robert"
second = "Allen"

# performs the swap
temp = first
first = second
second = temp

With iterable unpacking, the syntax could not be cleaner:

first = "Robert"
second = "Allen"

# performs the swap
first, second = second, first

In addition, another really common use of iterable unpacking is during looping. For example, sometimes folks like to approximate a for loop from their favorite C-style languages by using enumerate(). What enumerate() does is craft a sequence of tuples where the first element is the index and the second element is the data. Because the order is constant, we can take advantage of iterable unpacking:

watchlist = ["Loki", "Attack on Titan", "Vinland Saga"]
for index, show in enumerate(watchlist):
  print(index, show)

# prints
# 0 Loki
# 1 Attack on Titan
# 2 Vinland Saga

A similar trick can be used when you’re iterating over a dictionary using the items() method. Once you start to notice this trick, you’ll start seeing it everywhere.

Lastly, you might also know that Python let’s you return multiple values from a function by wrapping them in a tuple. When this happens, you might be tempted to store the tuple outright and index it for the values you need. Instead, I prefer to use iterable unpacking to get nice variables as follows

def grade_range(scores):
  return min(scores), max(scores)

grades = [71, 93, 54, 99, 80]
worst_grade, best_grade = grade_range(grades)

How cool is that? Next, we’ll look at how iterable unpacking works with custom iterables.

How Does Iterable Unpacking Work With Custom Iterables?

Previously, you may recall that we created a couple of our own custom iterables. Naturally, I’m interested in if these same iterables support iterable unpacking. To start, we’ll use a custom iterable that implements the __iter__() special method.

import random

class DiceRoll:

    def __init__(self):
        self.history = []

    def roll(self):
        total = random.randint(1,6) + random.randint(1,6)
        self.history.append(total)
        return total

    def __iter__(self):
        return iter(self.history)

After rolling the dice a few times, I attempted iterable unpacking, and it just worked:

test = DiceRoll()

test.roll() # returned 5
test.roll() # returned 11
test.roll() # returned 10
test.roll() # returned 2
test.roll() # returned 8

first, *middle, last = test # unpacked into 5, [11, 10, 2], 8

Out of curiosity, I also attempted it on the alternate implementation using __getitem__():

import random

class DiceRoll:

    def __init__(self):
        self.history = []

    def roll(self):
        total = random.randint(1,6) + random.randint(1,6)
        self.history.append(total)
        return total

    def __getitem__(self, key):
        return self.history[key]

Perhaps unsurprisingly, it worked just as well:

test = DiceRoll()

test.roll() # returned 6
test.roll() # returned 7
test.roll() # returned 4
test.roll() # returned 7
test.roll() # returned 6

first, *middle, last = test # stored 6, [7, 4, 7], and 6

Overall, I was extremely pleased with how robust the iterable and iterable unpacking features are in Python.

Up next, we’ll take a look at a brief history of the iterable unpacking feature.

Python Version History

It’s important to note that iterable unpacking has not been a permanent feature of Python, and it’s been modified over time. For example, the following lists some of the changes made to iterable unpacking since Python 3:

Unfortunately, prior to Python 3, iterable unpacking didn’t really exist to the extent it does now. Instead, folks were using a mix of indexing and slicing to manually unpack their iterables.

Unpacking More Concepts

Previously, we explored the idea of iterables, and today we looked at a feature of iterables called iterable unpacking. In the future, we’ll look to cover some of the related concepts that popped up today like slicing and indexing. Overall, I’m really enjoying watching this concept map grow! I hope you are too.

In the meantime, you’re welcome to browse some of these related articles:

Similarly, you can start learning Python with any of the following resources (#ad):

Finally, you can support the site by heading over to my list of ways to grow the site. Otherwise, take care!

The Python Concept Map (3 Articles)—Series Navigation

An activity I regularly do with my students is a concept map. Typically, we do it at the start and end of each semester to get an idea of how well our understanding of the material as matured over time. Naturally, I had the idea to extend this concept into its own series, just to see how deeply I can explore my own knowledge of Python. It should be a lot of fun, and I hope you enjoy it!

Jeremy Grifski

Jeremy grew up in a small town where he enjoyed playing soccer and video games, practicing taekwondo, and trading Pokémon cards. Once out of the nest, he pursued a Bachelors in Computer Engineering with a minor in Game Design. After college, he spent about two years writing software for a major engineering company. Then, he earned a master's in Computer Science and Engineering. Today, he pursues a PhD in Engineering Education in order to ultimately land a teaching gig. In his spare time, Jeremy enjoys spending time with his wife, playing Overwatch and Phantasy Star Online 2, practicing trombone, watching Penguins hockey, and traveling the world.

Recent Code Posts