Pythonic Patterns¶
Idiomatic Python patterns for clean, efficient, and readable code.
EAFP vs LBYL¶
Python favors "Easier to Ask Forgiveness than Permission" (EAFP) over "Look Before You Leap" (LBYL).
EAFP Style (Pythonic)¶
# Try the operation, handle exceptions
try:
value = dictionary[key]
except KeyError:
value = default
# File operations
try:
with open('file.txt') as f:
data = f.read()
except FileNotFoundError:
data = ""
LBYL Style (Less Pythonic)¶
# Check before operating
if key in dictionary:
value = dictionary[key]
else:
value = default
# File check
if os.path.exists('file.txt'):
with open('file.txt') as f:
data = f.read()
When to Use Each¶
- EAFP: When the operation usually succeeds
- LBYL: When checking is cheaper than exception handling
Context Managers¶
Use with statements for resource management:
# File handling
with open('file.txt') as f:
data = f.read()
# File automatically closed
# Multiple resources
with open('in.txt') as src, open('out.txt', 'w') as dst:
dst.write(src.read())
# Database connections
with connection.cursor() as cursor:
cursor.execute(query)
# Locks
with threading.Lock():
shared_resource.modify()
Comprehensions¶
List Comprehensions¶
# Pythonic
squares = [x**2 for x in range(10)]
evens = [x for x in numbers if x % 2 == 0]
# With condition
positive = [x for x in data if x > 0]
# Nested
matrix = [[i*j for j in range(3)] for i in range(3)]
Dictionary Comprehensions¶
# Create dict from lists
pairs = {k: v for k, v in zip(keys, values)}
# Transform dict
upper_dict = {k.upper(): v for k, v in original.items()}
# Filter dict
filtered = {k: v for k, v in data.items() if v > 0}
Set Comprehensions¶
unique_lengths = {len(word) for word in words}
Generator Expressions¶
# Memory efficient for large data
total = sum(x**2 for x in range(1000000))
# Lazy evaluation
gen = (expensive(x) for x in data)
Common Design Patterns¶
Factory Functions¶
def create_user(name, email, role='user'):
"""Factory function for user creation."""
return {
'name': name,
'email': email,
'role': role,
'created_at': datetime.now()
}
# Usage
admin = create_user('Alice', 'alice@example.com', role='admin')
Builder Pattern¶
class QueryBuilder:
def __init__(self):
self._table = None
self._columns = ['*']
self._conditions = []
def table(self, name):
self._table = name
return self
def select(self, *columns):
self._columns = columns
return self
def where(self, condition):
self._conditions.append(condition)
return self
def build(self):
query = f"SELECT {', '.join(self._columns)} FROM {self._table}"
if self._conditions:
query += f" WHERE {' AND '.join(self._conditions)}"
return query
# Fluent interface
query = (QueryBuilder()
.table('users')
.select('name', 'email')
.where('active = 1')
.build())
Registry Pattern¶
_handlers = {}
def register(name):
"""Decorator to register handlers."""
def decorator(func):
_handlers[name] = func
return func
return decorator
def get_handler(name):
return _handlers.get(name)
# Usage
@register('csv')
def process_csv(data):
return parse_csv(data)
@register('json')
def process_json(data):
return json.loads(data)
# Dispatch
handler = get_handler(file_type)
result = handler(data)
Assignment Patterns¶
Tuple Unpacking¶
# Multiple assignment
a, b = 1, 2
x, y, z = point
# Extended unpacking
first, *rest = [1, 2, 3, 4, 5]
head, *middle, tail = data
# Ignore values
_, important, _ = get_values()
first, *_, last = sequence
Swapping¶
# Pythonic swap (no temp variable)
a, b = b, a
# Multiple swaps
a, b, c = c, a, b
Walrus Operator¶
# Assign and test in one expression
if (n := len(data)) > 10:
print(f"Large dataset: {n} items")
# In while loops
while (line := file.readline()):
process(line)
# In list comprehensions
results = [y for x in data if (y := transform(x)) is not None]
Default Values¶
# Or pattern for defaults
name = user_input or "Anonymous"
config = options.get('config') or default_config
# Conditional expression
result = value if condition else default
Summary¶
| Pattern | Use Case | Example |
|---|---|---|
| EAFP | Operations that usually succeed | try/except |
| Context managers | Resource management | with open() |
| Comprehensions | Transform/filter collections | [x for x in data] |
| Factory functions | Object creation | create_user() |
| Registry | Plugin systems | @register('name') |
| Unpacking | Multiple assignment | a, *rest = data |
| Walrus | Assign in expressions | if (n := len(x)): |
Key principles: - Prefer EAFP for cleaner code - Always use context managers for resources - Use comprehensions for clarity and performance - Choose patterns that fit your problem