Skip to content
Happy Programming Guide
Start learning
Python

Python Tuple Comprehension: A Beginner’s Guide

Python has no tuple comprehension. Here is what the parentheses actually create, how to build a tuple from a comprehension, and when a generator is the better choice anyway.

Hands typing on a laptop keyboard

Here is the answer that saves you an hour: Python has no tuple comprehension. Writing (x * 2 for x in numbers) looks like it should produce a tuple, but it produces a generator. To get a tuple, wrap a comprehension in the tuple() function. This guide explains why the language works that way, and why the generator you accidentally created is often what you wanted all along.

What the parentheses actually do#

Python
numbers = [1, 2, 3, 4]

squares_list = [n ** 2 for n in numbers]     # list comprehension
squares_set = {n ** 2 for n in numbers}      # set comprehension
squares_dict = {n: n ** 2 for n in numbers}  # dict comprehension
squares_gen = (n ** 2 for n in numbers)      # generator expression

print(type(squares_list))   # <class 'list'>
print(type(squares_set))    # <class 'set'>
print(type(squares_dict))   # <class 'dict'>
print(type(squares_gen))    # <class 'generator'>   <- not a tuple

Square brackets and curly braces were free to be used for comprehensions. Parentheses were not — they were already doing several jobs (grouping expressions, calling functions, defining tuples), and Python’s designers gave them to generator expressions instead, because a lazy sequence is more useful more often than an immutable one.

How to actually build a tuple#

Python
numbers = [1, 2, 3, 4]

squares = tuple(n ** 2 for n in numbers)
print(squares)         # (1, 4, 9, 16)
print(type(squares))   # <class 'tuple'>

Note there is only one set of brackets. tuple(...) is a function call, and the generator expression sits inside it as the argument. You do not need the extra parentheses:

Python
tuple(n ** 2 for n in numbers)      # fine
tuple((n ** 2 for n in numbers))    # also fine, but redundant
tuple([n ** 2 for n in numbers])    # works, builds a throwaway list first

The third version is slightly wasteful: it constructs a complete list in memory, then copies it into a tuple, then throws the list away. Prefer the first.

Filtering and conditionals work the same#

Python
numbers = range(1, 11)

evens = tuple(n for n in numbers if n % 2 == 0)
print(evens)   # (2, 4, 6, 8, 10)

labels = tuple("even" if n % 2 == 0 else "odd" for n in range(1, 6))
print(labels)  # ('odd', 'even', 'odd', 'even', 'odd')

The if at the end filters; the if/else at the front chooses a value. That distinction confuses people in every comprehension, not just this one.

The bug this causes#

Because a generator is not a tuple, code that assumed a tuple starts behaving strangely:

Python
gen = (n ** 2 for n in [1, 2, 3])

print(len(gen))
Output
TypeError: object of type 'generator' has no len()

Worse is the version that does not raise anything:

Python
gen = (n ** 2 for n in [1, 2, 3])

print(sum(gen))   # 14
print(sum(gen))   # 0   <- not a bug in sum
print(list(gen))  # []

A generator produces its values once. After the first sum walked through it, there is nothing left. No error, just a zero, which is exactly the kind of bug that survives code review.

When you want the generator instead#

Generators are lazy: they compute each value only when asked. That makes them the right choice for large or infinite sequences, and for feeding functions that consume values one at a time.

Python
import sys

as_tuple = tuple(n for n in range(1_000_000))
as_gen = (n for n in range(1_000_000))

print(sys.getsizeof(as_tuple))   # roughly 8 MB
print(sys.getsizeof(as_gen))     # around 200 bytes

The generator has not computed anything yet. It holds only the instructions. That is why this pattern is efficient:

Python
# reads one line at a time, never loads the whole file
with open("big.log") as f:
    error_count = sum(1 for line in f if "ERROR" in line)

Wrapping that in tuple() would pull the entire file into memory to count it, which defeats the point.

Tuple or list?#

Once you have decided you want a concrete sequence rather than a generator, the tuple-versus-list question is about intent:

Tuple List
Can be changed after creation No Yes
Usable as a dictionary key Yes No
Usable in a set Yes No
Slightly smaller and faster Yes
Signals “fixed record” Yes

The dictionary-key point is the practical one. This is legal because the coordinates are tuples:

Python
grid = {}
grid[(0, 0)] = "start"
grid[(3, 4)] = "treasure"

corners = tuple((x, y) for x in (0, 9) for y in (0, 9))
print(corners)   # ((0, 0), (0, 9), (9, 0), (9, 9))

Questions people ask#

Why does Python not have a tuple comprehension?

Parentheses were already taken by generator expressions, which arrived later than list comprehensions and were judged more broadly useful. Since tuple(...) converts a generator in one short call, adding dedicated syntax would buy very little.

Is tuple(generator) slower than a list comprehension?

Marginally, because of the extra function call, but the difference is small enough to ignore in almost all code. Choose based on whether you want immutability, not on micro-benchmarks.

Can I use a generator expression as a function argument without extra brackets?

Yes, when it is the only argument: sum(n for n in nums) is valid. With more than one argument you need the parentheses: max((n for n in nums), default=0).

How do I check whether something is a generator?

Use import types then isinstance(obj, types.GeneratorType). In everyday code, calling type(obj) in a quick print is usually enough to settle it.

Where to go next#

Python lists explained, comprehensions includedRead next

Keep reading

Python

Python Basics

The core of Python in one page: variables, types, conditions, loops, functions and lists, each with a runnable example and the mistake…

4 min read

Keep going — pick your next guide

The fastest way to improve is to read one guide, then build the thing it describes. Start with the basics, or jump straight to a project.

Ask a question or share what worked

Your email address will not be published. Required fields are marked *