defaultdict
A familiar problem with dictionaries:
scores = {}
def increment_score_for_user(username: str):
if username not in scores:
scores[username] = 0
scores[username] += 1
But if we can only remember the existance of the humble defaultdict
, we can save ourselves the hassle:
from collections import defaultdict
scores = defaultdict(int)
def increment_score_for_user(username: str):
scores[username] += 1
The argument on the constructor is a function that defines the value to use if the provided key is not present in the dictionary:
from collections import defaultdict
starting_score = 15
scores = defaultdict(lambda: starting_score)
assert scores["some unknown user"] == 15
Generator expressions
An example of a list comprehension, getting the length of each line in a file:
def get_line_lengths(filename):
return [len(line) for line in open(filename)]
But then someone goes and does something like:
get_line_lengths("gigabyte-file.txt")
So we replace the list comprehension with a generator expression (in this case by simply replacing the square brackets with round):
def get_line_lengths(filename):
return (len(line) for line in open(filename))
Which returns a generator, which we can iterate over using next
:
g = get_line_lengths("gigabyte-file.txt")
next(g)
Note that this is equivalent to the generator function, which in a context like this might be considered clearer:
def get_line_lengths(filename):
for line in open(filename):
yield len(line)
Using a decorator to trace function calls
An example of how to write your own decorators:
from functools import wraps
import sys
def trace(func):
@wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
print(f"{func.__name__} was called with {args}", file=sys.stdout)
return result
return wrapper
@trace
def something(name: str):
print(f"Hello, {name}!")
Named tuples
The ugliness of accessing tuples by index makes my eyes bleed:
old_speckled_hen = ('Old Speckled Hen', "Ale", "500ml")
assert old_speckled_hen[2] == "500ml"
So save yourself a trip to the optometrist and use namedtuple
:
from collections import namedtuple
Beer = namedtuple('Beer', 'name type amount')
old_speckled_hen = Beer(name='Old Speckled Hen', type="Ale", amount="500ml")
super_dry = Beer(name='Asahi Super Dry', type="Lager", amount="620ml")
assert old_speckled_hen.amount == "500ml"
Adding a function-level cache with a single line of code
Not a complicated thing to achieve, but doing so using a single line of code built into the language is nice.
Obviously only for deterministic code.
from functools import lru_cache
@lru_cache
def process_file(filename: str):
# Some expensive function that is deterministic from the parameter...
return get_number_of_items_in_file(filename)