An on-going and unordered catalogue of useful Python stuff
Jun 1, 2020
2 minute read

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)

Thanks for reading! Please share this post if you found it useful, check out my other posts, and of course, consider buying me a coffee!


comments powered by Disqus