from "Effective Python" by Brett Slatkin
- Know which version of python you're using
- Follow the PEP 8 Style Guide
- Know the differences between bytes and str
- Prefer interpolated F-strings over C-style format strings and str.format
- Write helper functions instead of complex expressions
- Prefer multiple assignment unpacking over indexing
# ok
foo[:3], bar = some_function()
# better
*foo, bar = some_function()
- Prefer enumarate over range
# ok
for i in range(10):
...
# better
for indx, item in enumerate(list_of_things):
...
-
Use
zip
to process iterators in parallel -
Prevent repitition with assignment expressions
- Know how to slice sequences
# implicitly creates a max len to return
a = ['a', 'b', 'c', ['d']]
a[:20] # returns all entries
a[-20:] # returns all entries
# replacements don't need to be equal in size
a[:3] = ['e', 'f'] # a = ['e', 'f', 'd']
b = a # copies contents
b = a[:] # allocates a new list
- Avoid striding and slicing in a single expression
a = ['a', 'b', 'c', 'd']
# get odd entries
a[::2] # start:end:stride
# reverse a list or byte string
x = b'apples'
y= [::-1]
# avoid using all three args
a[1:3:2]
- Prefer catch-all unpacking over slicing
# ok
oldest = ages_descending[0]
second_oldest = ages_descending[1]
others = ages_descending[2:]
# better
oldest, second_oldest, *others = ages_descending
- Sort by complex criteria using the
key
parameter
# list type provides a sort method
numbers = [1,2,5,3,4,0]
numbers.sort()
# sort accepts a key parameter
tools = [toolClass, toolClass, toolClass]
tools.sort(key=lambda x : x.name)
tools.sort(key=lambda x : (x.name, x.weight)) # can sort by multiple attributes
tools.sort(key=lambda x : (x.name, x.weight), reverse=True)
tools.sort(key=lambda x : (x.name, -x.weight), reverse=True) # can mix orders like that
-
Be cautious when relying on dict insertion ordering
-
Prefer
get
overin
andKeyError
to handle missing dictionary keys -
Prefer
defaultdict
oversetdefault
to handle missing items in internal state -
Know how to construct key-dependent default values with
__missing__
- Never unpack more than three variables when functions return multiple values
- Prefer raisig exceptions over returning
None
- Know how closure interact with variable scope
- Reduce visual noise with variable positional arguments
- Provide optional behavior with keyword arguments
- Use
None
and docstrings to specify dynamic default arguments - Enforce clarity with keyword-only and positional-only arguments
- Define function decorators with
functools.wraps
- Use comprehensions instead of
map
andfilter
a = [1,2,3,4,5]
# ok
alt = map(lambda x: x ** 2, a)
# better
b = [x ** 2 for x in a]
# with filter
c = [x**2 for x in a if x % 2 == 0 ]
# dictionary comprehension
even_sq_dict = {x: x**2 for x in a if x % 2 == 0}
# set comprehension
three_cub_set = {x**3 for x in a if x % 3 == 0}
- Avoid more than two control subexpressions in comprehensions
# comprehensions support multiple levels of looping
matrix = [[1,2,3], [4,5,6], [7,8,9]]
flat = [x for row in matrix for x in row]
squared = [[x**2 for x in row] for row in matrix]
- Avoid repeated work in comprehensions by using assignment expressions
# dict comprehesions and walrus operator
found = {name: batches for name in order
if (batches := get_batches(stock.get(name, 0), 8))}
# note: the if statement is evaluated first in the comprehension
# if the walrus operator is in the comprehension part of the loop, then it leaks into containing scope
half = [(last := count // 2) for count in stock.values()]
print(f'last item of {half} is {last}') # last = half[-1]
# doesn't leak value of count to containing scope
half = [count // 2 for count in stock.values()]
- Consider using generators instead of returning lists
# why? avoids keeping the entire list in memory
def index_file(handle):
offset = 0
for line in handle:
if line:
yield offset
for letter in line:
offset += 1
if letter = ' ':
yield offset
with open(path) as f:
it = index_file(f)
results = itertools.islice(it, 0, 10)
# results is a list of length 10
- Be defensive when iterating over arguments
- Consider generator expressions for large list comphrensions
- Compose multiple generators with
yield from
- Avoid injecting data into generators with
send
- Avoid causin state transitions in generators with throw
- Compose classess instead of nesting many levels of built-in types
- Accept functinos instead of classes for simple interfaces
- Use
@classmethod
polymorphism to construct objects generically - Initialize parent classes with
super
- Consider composing functionality with mix-in classes
- Prefer public attributes over private ones
- Inherit from collections.abc for custome container types
- Use plain attributes instead of setter and getter methods
- Consider
@property
instead of refactoring attributes - Use descriptors for resuable
@property
methods - Use
__getattr__
,__getattribute__
, and__setattr
for lazy attributes - Validate subclasses with
__init_subclass__
- Register class existance wiht
__set_name__
- Annotate class attributes with
__set_name__
- Prefer class decorators over metaclasses for composable class extensions
- Use
subprocess
to manage child processes