Python list comprehension 성능 비교
Benchmarks and analysis of Python list comprehensions vs loops vs map/filter — when comprehensions are faster, when they are not, and common pitfalls.
Python list comprehension 성능 비교
Introduction
List comprehensions are often cited as one of Python's performance optimizations — a way to create lists faster than traditional for loops. But this claim is nuanced. List comprehensions are not always faster, and in some cases they can consume more memory or be less readable than alternatives.
I benchmarked list comprehensions extensively while optimizing a data processing pipeline in Lisbon. The pipeline processed millions of financial records, and the difference between a well-written comprehension and a naive loop was significant — but not always in the direction you might expect.
Environment
Python 3.12.3
timeit 1.3.2 (built-in)
Linux 6.5.0
Intel Core i7-12700H
16GB RAMProblem
The common belief — comprehensions are always faster:
import timeit
data = list(range(100000))
# Method 1: for loop
def with_loop(data):
result = []
for x in data:
if x % 2 == 0:
result.append(x * 2)
return result
# Method 2: list comprehension
def with_comprehension(data):
return [x * 2 for x in data if x % 2 == 0]
# Method 3: map/filter
def with_map_filter(data):
return list(map(lambda x: x * 2, filter(lambda x: x % 2 == 0, data)))
# Benchmark
print("Loop: ", timeit.timeit(lambda: with_loop(data), number=100))
print("Comprehension:", timeit.timeit(lambda: with_comprehension(data), number=100))
print("Map/Filter: ", timeit.timeit(lambda: with_map_filter(data), number=100))Typical results:
Loop: 0.847
Comprehension: 0.523
Map/Filter: 0.712But what about complex operations?
data = list(range(10000))
# Complex comprehension — harder to read
result = [
(x, y, x * y)
for x in range(100)
for y in range(100)
if x != y
if (x * y) % 3 == 0
]
# Same thing with loops — clearer
result = []
for x in range(100):
for y in range(100):
if x != y and (x * y) % 3 == 0:
result.append((x, y, x * y))Memory consumption comparison:
import sys
data = list(range(1000000))
# List comprehension — creates full list in memory
comp = [x * 2 for x in data]
print(f"Comprehension: {sys.getsizeof(comp)} bytes")
# Generator expression — lazy evaluation
gen = (x * 2 for x in data)
print(f"Generator: {sys.getsizeof(gen)} bytes")
# Map — also lazy
mapped = map(lambda x: x * 2, data)
print(f"Map: {sys.getsizeof(mapped)} bytes")Analysis
List comprehensions are faster than for loops for simple operations because:
- The iteration happens in C. The
forloop in a comprehension is executed by the Python interpreter, but the list construction is done in optimized C code. - No method lookups.
result.append(x)requires a method lookup and call on every iteration. The comprehension avoids this. - No bytecode overhead. The comprehension compiles to a single
LIST_APPENDbytecode instruction per element.
When comprehensions are NOT faster:
- Complex operations. When the operation inside the comprehension is expensive, the overhead of the loop becomes negligible.
- Side effects. When you need to perform side effects (print, write to file), a regular loop is clearer.
- Nested comprehensions. Deeply nested comprehensions are harder to read and not significantly faster.
Memory considerations:
- List comprehensions create the entire list in memory at once
- Generator expressions are lazy and use minimal memory
- For large datasets, generators are often better
Solution
Benchmark 1: Simple transformation
import timeit
data = list(range(100000))
# For loop
def with_loop():
result = []
for x in data:
result.append(x * 2)
return result
# List comprehension
def with_comp():
return [x * 2 for x in data]
# Map
def with_map():
return list(map(lambda x: x * 2, data))
print("Loop: ", timeit.timeit(with_loop, number=100))
print("Comprehension:", timeit.timeit(with_comp, number=100))
print("Map: ", timeit.timeit(with_map, number=100))Benchmark 2: Filter + transform
import timeit
data = list(range(100000))
# For loop with condition
def with_loop():
result = []
for x in data:
if x % 2 == 0:
result.append(x * 2)
return result
# List comprehension
def with_comp():
return [x * 2 for x in data if x % 2 == 0]
# Filter + map
def with_filter_map():
return list(map(lambda x: x * 2, filter(lambda x: x % 2 == 0, data)))
print("Loop: ", timeit.timeit(with_loop, number=100))
print("Comprehension:", timeit.timeit(with_comp, number=100))
print("Filter+Map:", timeit.timeit(with_filter_map, number=100))Benchmark 3: Large data — memory comparison
import sys
import timeit
# Small dataset
small = list(range(1000))
comp_small = [x * 2 for x in small]
gen_small = (x * 2 for x in small)
print(f"Small - List: {sys.getsizeof(comp_small)} bytes, Generator: {sys.getsizeof(gen_small)} bytes")
# Large dataset
large = list(range(1000000))
comp_large = [x * 2 for x in large]
gen_large = (x * 2 for x in large)
print(f"Large - List: {sys.getsizeof(comp_large)} bytes, Generator: {sys.getsizeof(gen_large)} bytes")Best practices decision tree:
# Use list comprehension for:
# - Simple transformations
# - Filtering with simple conditions
# - When you need the full list in memory
# Use generator expression for:
# - Large datasets
# - Chaining operations (pipeline)
# - When you only need to iterate once
# Use for loop for:
# - Complex logic
# - Side effects
# - Debugging (easier to step through)
# - Multiple exit conditions
# Example of generator pipeline
data = range(1000000)
result = sum(x * 2 for x in data if x % 2 == 0) # Generator — memory efficientLessons Learned
- List comprehensions are generally faster than
forloops for simple operations due to C-level optimization. - Use generator expressions when processing large datasets to save memory.
- Avoid deeply nested comprehensions — readability matters more than micro-optimization.
- Benchmark your specific use case — the performance difference varies with data size and operation complexity.
- For complex operations, a regular
forloop is often clearer and no significantly slower.
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.