The timeit module provides accurate timing by running code multiple times and disabling garbage collection. Essential for comparing implementations and optimizing hot paths.
Command Line Usage
# Time a simple expression
python -m timeit '"-".join(str(n) for n in range(100))'
# 10000 loops, best of 5: 25.3 usec per loop
# Time with setup
python -m timeit -s 'import random' 'random.random()'
# 5000000 loops, best of 5: 50.4 nsec per loop
# Specify number of executions
python -m timeit -n 1000000 'x = 1 + 1'Basic Python Usage
import timeit
# Time a string statement
time = timeit.timeit('x = sum(range(100))', number=100000)
print(f"Total: {time:.3f}s")
print(f"Per call: {time/100000*1e6:.2f}µs")
# With setup code
time = timeit.timeit(
stmt='random.random()',
setup='import random',
number=1000000
)Timing Functions
import timeit
def my_function():
return sum(range(100))
# Time a callable
time = timeit.timeit(my_function, number=100000)
print(f"{time/100000*1e6:.2f}µs per call")
# Or use lambda
time = timeit.timeit(lambda: sum(range(100)), number=100000)repeat() for Statistical Accuracy
import timeit
# Run multiple trials
times = timeit.repeat(
stmt='"-".join(map(str, range(100)))',
setup='',
repeat=5, # Number of trials
number=10000 # Executions per trial
)
print(f"Best: {min(times)/10000*1e6:.2f}µs")
print(f"Worst: {max(times)/10000*1e6:.2f}µs")
print(f"Mean: {sum(times)/len(times)/10000*1e6:.2f}µs")Timer Class
import timeit
# Create reusable timer
timer = timeit.Timer(
stmt='sorted(data)',
setup='import random; data = [random.random() for _ in range(1000)]'
)
# Auto-determine number of runs
number, time = timer.autorange()
print(f"{number} loops, {time/number*1e6:.2f}µs per loop")
# Run specific number
times = timer.repeat(repeat=3, number=1000)Comparing Implementations
import timeit
# List comprehension vs map
setup = 'data = list(range(1000))'
list_comp = timeit.timeit(
'[x*2 for x in data]',
setup=setup,
number=10000
)
map_func = timeit.timeit(
'list(map(lambda x: x*2, data))',
setup=setup,
number=10000
)
print(f"List comp: {list_comp:.3f}s")
print(f"Map: {map_func:.3f}s")
print(f"Winner: {'List comp' if list_comp < map_func else 'Map'}")Passing Local Variables
import timeit
def benchmark_sort(data_size):
setup = f'import random; data = [random.random() for _ in range({data_size})]'
time = timeit.timeit('sorted(data)', setup=setup, number=100)
return time / 100
# Or use globals
data = list(range(1000))
time = timeit.timeit('sorted(data)', globals=globals(), number=1000)Decorator for Quick Timing
import timeit
from functools import wraps
def benchmark(func):
@wraps(func)
def wrapper(*args, **kwargs):
timer = timeit.Timer(lambda: func(*args, **kwargs))
n, total = timer.autorange()
print(f"{func.__name__}: {total/n*1e6:.2f}µs ({n} loops)")
return func(*args, **kwargs)
return wrapper
@benchmark
def my_function(n):
return sum(range(n))
my_function(1000)Context Manager Timing
import timeit
from contextlib import contextmanager
@contextmanager
def timed(label="Block"):
timer = timeit.default_timer
start = timer()
try:
yield
finally:
elapsed = timer() - start
print(f"{label}: {elapsed*1000:.2f}ms")
with timed("Sort operation"):
sorted(range(100000, 0, -1))Comparing Data Structures
import timeit
def compare_lookup():
setup_list = 'data = list(range(10000))'
setup_set = 'data = set(range(10000))'
setup_dict = 'data = {i: i for i in range(10000)}'
list_time = timeit.timeit('9999 in data', setup=setup_list, number=100000)
set_time = timeit.timeit('9999 in data', setup=setup_set, number=100000)
dict_time = timeit.timeit('9999 in data', setup=setup_dict, number=100000)
print(f"List lookup: {list_time:.4f}s")
print(f"Set lookup: {set_time:.4f}s")
print(f"Dict lookup: {dict_time:.4f}s")
compare_lookup()
# List lookup: 0.8234s
# Set lookup: 0.0089s <- O(1) vs O(n)
# Dict lookup: 0.0091sString Concatenation
import timeit
n = 10000
# Plus operator
plus = timeit.timeit(
's = ""; exec("for i in range(100): s += str(i)")',
number=n
)
# Join
join = timeit.timeit(
'"".join(str(i) for i in range(100))',
number=n
)
# F-string (fixed content)
fstring = timeit.timeit(
'f"{1}{2}{3}{4}{5}"',
number=n
)
print(f"Plus: {plus:.3f}s")
print(f"Join: {join:.3f}s")
print(f"F-string: {fstring:.3f}s")Avoiding Pitfalls
import timeit
# BAD: Including import in timing
bad = timeit.timeit('import json; json.dumps({"a": 1})', number=10000)
# GOOD: Setup imports separately
good = timeit.timeit(
'json.dumps({"a": 1})',
setup='import json',
number=10000
)
# BAD: Not enough iterations
unreliable = timeit.timeit('x = 1 + 1', number=1)
# GOOD: Let autorange decide
timer = timeit.Timer('x = 1 + 1')
n, _ = timer.autorange()Multiline Statements
import timeit
code = '''
result = []
for i in range(100):
if i % 2 == 0:
result.append(i * 2)
'''
time = timeit.timeit(code, number=10000)
print(f"Loop: {time:.3f}s")
# Compare to comprehension
comp = timeit.timeit(
'[i * 2 for i in range(100) if i % 2 == 0]',
number=10000
)
print(f"Comprehension: {comp:.3f}s")CLI Options
# Basic timing
python -m timeit 'stmt'
# With setup
python -m timeit -s 'setup' 'stmt'
# Specify iterations
python -m timeit -n 1000 'stmt'
# Specify repeats
python -m timeit -r 5 'stmt'
# Time in seconds (vs per-loop)
python -m timeit -u sec 'stmt'
# Verbose output
python -m timeit -v 'stmt'Best Practices
| Do | Don't |
|---|---|
Use setup for imports | Include imports in stmt |
| Run many iterations | Time single execution |
Use repeat() for variance | Trust single measurement |
| Compare relative times | Focus on absolute times |
| Test realistic data sizes | Benchmark edge cases only |
| Disable GC (default) | Benchmark with GC artifacts |
When to Use What
# Quick check: timeit CLI
# python -m timeit 'code'
# Script comparison: timeit.timeit()
timeit.timeit('code', number=10000)
# Statistical analysis: timeit.repeat()
timeit.repeat('code', repeat=5, number=10000)
# Auto-scaling: Timer.autorange()
timer = timeit.Timer('code')
timer.autorange()timeit gives you reliable microbenchmarks. Use it to compare implementations before optimizing.
React to this post: