Iterables and Generators
Last updated on 2026-06-23 | Edit this page
Estimated time: 12 minutes
Overview
Questions
- What is an iterable in Python?
- What is a generator in Python?
- How can I succinctly create a list or dictionary in Python?
- What are sets and how can I use them in Python?
Objectives
- Understand what makes an object iterable in Python.
- Learn how to create and use generators in Python.
- Understand the difference between iterables and generators.
- Learn how to use set operations.
- Understand how a
defaultdictworks and how to use it.
What is an Iterable?
This might be a new term, but you’ve certainly been working with
iterables as soon as you started learning python. Anything that you can
loop over in a for loop is an iterable, so this includes
basic data structures like lists, tuples, and dictionaries, as well as
strings.
More specifically, an iterable is any Python object that implements
the __iter__() method, which returns an iterator. An
iterator is an object that implements the __next__()
method, which returns the next item in the sequence when called.
What’s happening under the hood?
You may have heard that in python “Everything is an object”. This is
true, and it means that even basic data structures like lists and
dictionaries are actually objects that have their own defined methods
and attributes. You’ve probably already used some of these methods, like
when you use my_list.append(1) to add an item to a list or
"my_string".upper() to convert a string to uppercase.
The convention in python is to use the __ (double
underscore) to denote special methods that are not intended to be called
directly by the user. These are often called “dunder” methods (short for
“double underscore”). When you use a for loop to iterate
over an iterable, Python is actually calling the __iter__()
method of the iterable to get an iterator, and then calling the
__next__() method of the iterator to get each item in the
sequence.
We can test this in action by creating a simple list and using these methods directly:
List Comprehensions
Say we wanted to create a list of the squares of the numbers from 0
to 9. We could do this with a for loop like this:
However there’s a more concise way to do this using what’s called a list comprehension:
What’s going on here? This is called a list comprehension, and it’s a
concise way to create lists. The syntax is
[expression for item in iterable], where
expression is the value that will be added to the list for
each item in the iterable.
List comprehensions can also include an optional if
statement to filter items from the iterable. For example, if we only
wanted the squares of the even numbers from 0 to 9, we could do
this:
List comprehensions are very useful for creating lists, but if overused or used in complex ways, they can make your code harder to read. It’s important to strike a balance between conciseness and readability.
Dictionary Comprehensions
Related to list comprehensions are dictionary comprehensions, which
allow you to create dictionaries in a similar way. The syntax for a
dictionary comprehension is
{key_expression: value_expression for item in iterable}.
Generator Functions
A generator is a special type of iterable that allows you to generate values on the fly, rather than storing them all in memory all at once. This can be extremely useful when working with large data, or when you want to create an infinite sequence of values.
You can create a generator using a generator function, which is
defined like a normal function but uses the yield keyword
instead of return. When you call a generator function, it
returns a generator object, which is an iterator that can be used to
iterate over the values generated by the function.
Here’s an example of a simple generator function that performs a countdown:
When you call this function, it returns a generator object:
<generator object countdown at 0x000001238D3EBDC0>
But if we call this generator in a for loop, it will
yield the values one at a time:
5
4
3
2
1
Blast off!
We can also use the next() function to get the next
value from the generator:
PYTHON
counter = countdown(5)
print(next(counter)) # This will print 5
print(next(counter)) # This will print 4
Generator Comprehensions
Just like we have list comprehensions and dictionary comprehensions, we also have generator comprehensions. The syntax for a generator comprehension is similar to a list comprehension, but it uses parentheses instead of square brackets:
This creates a generator that will yield the squares of the numbers
from 0 to 9. You can iterate over this generator in a for
loop or use the next() function to get the next value.
Sets and Set Operations
A set is an unordered collection of unique elements. In Python, you
can create a set using the set() function or by using curly
braces {}. For example:
Sets work just like lists, but they have some important differences that makes them very useful in certain situations:
- Sets are unordered, which means that the elements in a set do not have a specific order. This means that you cannot access elements in a set using an index like you can with a list.
- Sets do not allow duplicate elements. If you try to add a duplicate element to a set, it will simply be ignored.
- Sets support mathematical set operations like union, intersection, difference, and symmetric difference.
This makes sets very useful for tasks like removing duplicates from a list, checking for membership, and checking for overlap between two collections of data.
For a toy example, let’s say we are running a survey asking people what their favorite fruit is, and we want to find out just how many different fruits are in the list. We can use a set to do this:
PYTHON
fruit_survey_1 = ["apple", "banana", "orange", "apple", "grape", "banana"]
survey_1_set = set(fruit_survey_1)
print(survey_1_set)
{'grape', 'banana', 'orange', 'apple'}
Then, let’s imagine we perform the survey again with a different group of people, and we want to compare the results to see which fruits are popular across both surveys. We can use set operations to do this:
PYTHON
fruit_survey_2 = ["banana", "kiwi", "grape", "melon", "orange", "kiwi"]
survey_2_set = set(fruit_survey_2)
# Find the union of the two sets (all fruits from both surveys)
all_unique_fruits = survey_1_set.union(survey_2_set)
print("Fruits in either survey:", all_unique_fruits)
# Find the intersection of the two sets (fruits that are in both surveys)
common_fruits = survey_1_set.intersection(survey_2_set)
print("Fruits in both surveys:", common_fruits)
# Find only the fruits that are in survey 1 but not in survey 2
unique_to_survey_1 = survey_1_set.difference(survey_2_set)
print("Fruits only in survey 1:", unique_to_survey_1)
Output:
Fruits in either survey: {'banana', 'kiwi', 'apple', 'melon', 'grape', 'orange'}
Fruits in both surveys: {'banana', 'orange', 'grape'}
Fruits only in survey 1: {'apple'}
Sets operate broadly similarly to lists, but the functions used to manipulate them are slightly different:
-
add()- Adds an element to the set. (instead ofappend()for lists) -
remove()- Removes an element from the set. (instead ofpop()for lists) -
union()- Returns a new set with all elements from both sets. (instead ofextend()for lists)
Remember that the syntax for a dictionary comprehension is
{key_expression: value_expression for item in iterable if condition}.
Challenge: Jagged Zips
What happens if we try to zip two iterables of different lengths? Take a look at the following code. What do you think will happen?
PYTHON
x_values = ["a", "b", "c"]
y_values = [1, 2]
for x, y in zip(x_values, y_values):
print(x, y)
- It will raise an error because the iterables are of different lengths.
- It will zip the iterables together, but only up to the length of the shorter iterable.
- It will zip the iterables together, and fill in missing values with
None.
The correct answer is 2. The zip function will zip the
iterables together, but only up to the length of the shorter iterable.
This means that when zipping, you don’t need to necessarily have the
same number of elements in each iterable, but you need to keep this in
mind, since there is no explicit error raised.
Challenge: Set Operations
Here are two lists of students who attended two different classes. Use set operations to find out:
- Which students attended both classes?
- Which students attended only the first class?
- Which students attended only the second class?
- How many unique students attended at least one of the classes?
PYTHON
class_a_set = set(class_a_students)
class_b_set = set(class_b_students)
# Students who attended both classes
both_classes = class_a_set.intersection(class_b_set)
print("Students who attended both classes:", both_classes)
# Students who attended only the first class
only_class_a = class_a_set.difference(class_b_set)
print("Students who attended only the first class:", only_class_a)
# Students who attended only the second class
only_class_b = class_b_set.difference(class_a_set)
print("Students who attended only the second class:", only_class_b)
# Unique students who attended at least one class
num_unique_students = len(class_a_set.union(class_b_set))
print("Unique students who attended at least one class:", num_unique_students)
- An iterable is any Python object that implements the
__iter__()method, which returns an iterator. - Iterators also implement the
__next__()method, which returns the next item in the sequence when called. - A generator is a special type of iterable that allows you to generate values on the fly, rather than storing them all in memory at once.
- Sets are unordered collections of unique elements that support mathematical set operations like union, intersection, difference, and symmetric difference.