| name | python-idioms |
| description | Pythonic idioms - itertools, descriptors, 'there must be a better way' |
Raymond Hettinger - Pythonic Idioms
Apply Raymond Hettinger's teaching style and Python expertise. Core Python developer, famous for transforming verbose code into elegant, idiomatic Python.
Core Philosophy
"There Must Be a Better Way"
Hettinger's signature phrase. When code feels verbose or awkward, Python probably has a better pattern.
for i in range(len(items)):
print(i, items[i])
for i, item in enumerate(items):
print(i, item)
Itertools for Everything
from itertools import chain, groupby, islice, cycle, combinations
flat = list(chain.from_iterable(nested_lists))
for key, group in groupby(sorted(data, key=keyfunc), keyfunc):
process(key, list(group))
first_ten = list(islice(generator, 10))
for a, b in combinations(items, 2):
compare(a, b)
Prescriptive Rules
Use Named Tuples for Data
point = (3, 4)
print(point[0], point[1])
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
point = Point(3, 4)
print(point.x, point.y)
from typing import NamedTuple
class Point(NamedTuple):
x: float
y: float
Descriptors Over Property Repetition
class Circle:
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("Must be positive")
self._radius = value
class Positive:
def __set_name__(self, owner, name):
self.name = name
self.private = f'_{name}'
def __get__(self, obj, type=None):
return getattr(obj, self.private, None)
def __set__(self, obj, value):
if value < 0:
raise ValueError(f"{self.name} must be positive")
setattr(obj, self.private, value)
class :
radius = Positive()
diameter = Positive()
Defaultdict and Counter
word_count = {}
for word in words:
if word not in word_count:
word_count[word] = 0
word_count[word] += 1
from collections import Counter
word_count = Counter(words)
groups = {}
for item in items:
key = get_key(item)
if key not in groups:
groups[key] = []
groups[key].append(item)
from collections import defaultdict
groups = defaultdict(list)
for item in items:
groups[get_key(item)].append(item)
Context Managers for Resources
from contextlib import contextmanager
@contextmanager
def timer(name):
start = time.time()
yield
print(f"{name}: {time.time() - start:.2f}s")
with timer("processing"):
do_work()
Decorator Patterns
from functools import wraps, lru_cache
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@lru_cache(maxsize=128)
def expensive_calculation(n):
return sum(range(n))
Hettinger's Greatest Hits
Transform Loops
result = []
for x in items:
if condition(x):
result.append(x)
result = [x for x in items if condition(x)]
result = list(filter(condition, items))
result = []
for x in items:
result.append(transform(x))
result = [transform(x) for x in items]
result = list(map(transform, items))
Multiple Assignment
a, b = b, a
first, *rest = items
first, *middle, last = items
_, important, _ = get_triple()
Dictionary Patterns
merged = dict1 | dict2
value = d.get(key, default)
d.setdefault(key, []).append(value)
squared = {x: x**2 for x in range(10)}
Anti-Patterns
| Pattern | Hettinger Fix |
|---|
for i in range(len(x)) | for i, item in enumerate(x) |
dict.keys() iteration | Just for key in dict |
| Manual counter in loop | enumerate() or Counter |
if x in dict.keys() | if x in dict |
| Building string in loop | ''.join(parts) |
lambda x: func(x) | Just func |
Review Checklist
Before shipping Python code:
Key Quotes
"There must be a better way."
"If the implementation is hard to explain, it's a bad idea."
"Transforming code to be more Pythonic is not about fewer lines—it's about clearer intent."