Skip to content

ec1340/better-python

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 

Repository files navigation

Effictive Python Examples

from "Effective Python" by Brett Slatkin

Pythonic Thinking

  1. Know which version of python you're using
  2. Follow the PEP 8 Style Guide
  3. Know the differences between bytes and str
  4. Prefer interpolated F-strings over C-style format strings and str.format
  5. Write helper functions instead of complex expressions
  6. Prefer multiple assignment unpacking over indexing
# ok
foo[:3], bar = some_function()

# better
*foo, bar = some_function()
  1. Prefer enumarate over range
 # ok
for i in range(10):
    ...

# better
for indx, item in enumerate(list_of_things):
    ...
  1. Use zip to process iterators in parallel

  2. Prevent repitition with assignment expressions

Lists and Dictionaries

  1. 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
  1. 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]
  1. 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   
  1. 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
  1. Be cautious when relying on dict insertion ordering

  2. Prefer get over in and KeyError to handle missing dictionary keys

  3. Prefer defaultdict over setdefault to handle missing items in internal state

  4. Know how to construct key-dependent default values with __missing__

Functions

  1. Never unpack more than three variables when functions return multiple values
  2. Prefer raisig exceptions over returning None
  3. Know how closure interact with variable scope
  4. Reduce visual noise with variable positional arguments
  5. Provide optional behavior with keyword arguments
  6. Use None and docstrings to specify dynamic default arguments
  7. Enforce clarity with keyword-only and positional-only arguments
  8. Define function decorators with functools.wraps

Comprehensions and Generators

  1. Use comprehensions instead of map and filter
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}
  1. 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]
  1. 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()]
  1. 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
  1. Be defensive when iterating over arguments
  2. Consider generator expressions for large list comphrensions
  3. Compose multiple generators with yield from
  4. Avoid injecting data into generators with send
  5. Avoid causin state transitions in generators with throw

Classes and Interfaces

  1. Compose classess instead of nesting many levels of built-in types
  2. Accept functinos instead of classes for simple interfaces
  3. Use @classmethod polymorphism to construct objects generically
  4. Initialize parent classes with super
  5. Consider composing functionality with mix-in classes
  6. Prefer public attributes over private ones
  7. Inherit from collections.abc for custome container types

Metaclasses and Attributes

  1. Use plain attributes instead of setter and getter methods
  2. Consider @property instead of refactoring attributes
  3. Use descriptors for resuable @property methods
  4. Use __getattr__, __getattribute__, and __setattr for lazy attributes
  5. Validate subclasses with __init_subclass__
  6. Register class existance wiht __set_name__
  7. Annotate class attributes with __set_name__
  8. Prefer class decorators over metaclasses for composable class extensions

Concurrency and Parallelism

  1. Use subprocess to manage child processes

Robustness and Performance

Testing and Debugging

Collaboration

About

lessons from Brett Slatkin's Effective Python 2nd edition

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published