Gotchas of early-bound function argument defaults in Python

· 4 min

I was reading a tweet about it yesterday and that didn’t stop me from pushing a code change in production with the same rookie mistake today. Consider this function:

# src.py
from __future__ import annotations

import logging
import time
from datetime import datetime


def log(
    message: str,
    /,
    *,
    level: str,
    timestamp: str = datetime.utcnow().isoformat(),
) -> None:
    logger = getattr(logging, level)

    # Avoid f-string in logging as it's not lazy.
    logger("Timestamp: %s \nMessage: %s\n" % (timestamp, message))


if __name__ == "__main__":
    for _ in range(3):
        time.sleep(1)
        log("Reality can often be disappointing.", level="warning")

Here, the function log has a parameter timestamp that computes its default value using the built-in datetime.utcnow().isoformat() method. I was under the impression that the timestamp parameter would be computed each time when the log function was called. However, that’s not what happens when you try to run it. If you run the above snippet, you’ll get this instead:

Use 'assertIs' to check literal booleans in Python unittest

· 1 min

I used to use Unittest’s self.assertTrue / self.assertFalse to check both literal booleans and truthy/falsy values in Unittest. Committed the same sin while writing tests in Django.

I feel like assertTrue and assertFalse are misnomers. They don’t specifically check literal booleans, only truthy and falsy states respectively.

Consider this example:

# src.py
import unittest


class TestFoo(unittest.TestCase):
    def setUp(self):
        self.true_literal = True
        self.false_literal = False
        self.truthy = [True]
        self.falsy = []

    def is_true(self):
        self.assertTrue(self.true_literal, True)

    def is_false(self):
        self.assertFalse(self.false_literal, True)

    def is_truthy(self):
        self.assertTrue(self.truthy, True)

    def is_falsy(self):
        self.assertFalse(self.falsy, True)


if __name__ == "__main__":
    unittest.main()

In the above snippet, I’ve used assertTrue and assertFalse to check both literal booleans and truthy/falsy values. However, to test the literal boolean values, assertIs works better and is more explicit. Here’s how to do the above test properly:

Static typing Python decorators

· 5 min

Accurately static typing decorators in Python is an icky business. The wrapper function obfuscates type information required to statically determine the types of the parameters and the return values of the wrapped function.

Let’s write a decorator that registers the decorated functions in a global dictionary during function definition time. Here’s how I used to annotate it:

# src.py
# Import 'Callable' from 'typing' module in < Py3.9.
from collections.abc import Callable
from functools import wraps
from typing import Any, TypeVar

R = TypeVar("R")

funcs = {}


def register(func: Callable[..., R]) -> Callable[..., R]:
    """Register any function at definition time in
    the 'funcs' dict."""

    # Registers the function during function defition time.
    funcs[func.__name__] = func

    @wraps(func)
    def inner(*args: Any, **kwargs: Any) -> Any:
        return func(*args, **kwargs)

    return inner


@register
def hello(name: str) -> str:
    return f"Hello {name}!"

The functools.wraps decorator makes sure that the identity and the docstring of the wrapped function don’t get gobbled up by the decorator. This is syntactically correct and if you run Mypy against the code snippet, it’ll happily tell you that everything’s alright. However, this doesn’t exactly do anything. If you call the hello function with the wrong type of parameter, Mypy won’t be able to detect the mistake statically. Notice this:

Inspect docstrings with Pydoc

· 2 min

How come I didn’t know about the python -m pydoc command before today!

It lets you inspect the docstrings of any modules, classes, functions, or methods in Python.

I’m running the commands from a Python 3.10 virtual environment but it’ll work on any Python version. Let’s print out the docstrings of the functools.lru_cache function. Run:

python -m pydoc functools.lru_cache

This will print the following on the console:

Help on function lru_cache in functools:

functools.lru_cache = lru_cache(maxsize=128, typed=False)
    Least-recently-used cache decorator.

    If *maxsize* is set to None, the LRU features are disabled and
    the cache can grow without bound.

    If *typed* is True, arguments of different types will be cached
    separately. For example, f(3.0) and f(3) will be treated as
    distinct calls with distinct results.

    Arguments to the cached function must be hashable.

    View the cache statistics named tuple (hits, misses, maxsize,
    currsize) with f.cache_info().  Clear the cache and statistics
    with f.cache_clear(). Access the underlying function with
    f.__wrapped__.

Works for third party tools as well:

Check whether an integer is a power of two in Python

· 2 min

To check whether an integer is a power of two, I’ve deployed hacks like this:

def is_power_of_two(x: int) -> bool:
    return x > 0 and hex(x)[-1] in ("0", "2", "4", "8")

While this hex trick works, I’ve never liked explaining the pattern matching hack that’s going on here.

Today, I came across this tweet by Raymond Hettinger where he proposed an elegant solution to the problem. Here’s how it goes:

def is_power_of_two(x: int) -> bool:
    return x > 0 and x.bit_count() == 1

This is neat as there’s no hack and it uses a mathematical invariant to check whether an integer is a power of 2 or not. Also, it’s a tad bit faster.

Uniform error response in Django Rest Framework

· 3 min

Django Rest Framework exposes a neat hook to customize the response payload of your API when errors occur. I was going through Microsoft’s REST API guideline and wanted to make the error response of my APIs more uniform and somewhat similar to this example.

I’ll use a modified version of the quickstart example in the DRF docs to show how to achieve that. Also, we’ll need a POST API to demonstrate the changes better. Here’s the same example with the added POST API. Place this code in the project’s urls.py file.

Difference between constrained 'TypeVar' and 'Union' in Python

· 2 min

If you want to define a variable that can accept values of multiple possible types, using typing.Union is one way of doing that:

from typing import Union

U = Union[int, str]

However, there’s another way you can express a similar concept via constrained TypeVar. You’d do so as follows:

from typing import TypeVar

T = TypeVar("T", int, str)

So, what’s the difference between these two and when to use which? The primary difference is:

T’s type needs to be consistent across multiple uses within a given scope, while U’s doesn’t.

Don't wrap instance methods with 'functools.lru_cache' decorator in Python

· 6 min

Recently, fell into this trap as I wanted to speed up a slow instance method by caching it.

When you decorate an instance method with functools.lru_cache decorator, the instances of the class encapsulating that method never get garbage collected within the lifetime of the process holding them.

Let’s consider this example:

# src.py
import functools
import time
from typing import TypeVar

Number = TypeVar("Number", int, float, complex)


class SlowAdder:
    def __init__(self, delay: int = 1) -> None:
        self.delay = delay

    @functools.lru_cache
    def calculate(self, *args: Number) -> Number:
        time.sleep(self.delay)
        return sum(args)

    def __del__(self) -> None:
        print("Deleting instance ...")


# Create a SlowAdder instance.
slow_adder = SlowAdder(2)

# Measure performance.
start_time = time.perf_counter()
# ----------------------------------------------
result = slow_adder.calculate(1, 2)
# ----------------------------------------------
end_time = time.perf_counter()
print(f"Calculation took {end_time-start_time} seconds, result: {result}.")


start_time = time.perf_counter()
# ----------------------------------------------
result = slow_adder.calculate(1, 2)
# ----------------------------------------------
end_time = time.perf_counter()
print(f"Calculation took {end_time-start_time} seconds, result: {result}.")

Here, I’ve created a simple SlowAdder class that accepts a delay value; then it sleeps for delay seconds and calculates the sum of the inputs in the calculate method. To avoid this slow recalculation for the same arguments, the calculate method was wrapped in the lru_cache decorator. The __del__ method notifies us when the garbage collection has successfully cleaned up instances of the class.

Cropping texts in Python with 'textwrap.shorten'

· 3 min

Problem

A common interview question that I’ve seen goes as follows:

Write a function to crop a text corpus without breaking any word.

  • Take the length of the text up to which character you should trim.
  • Make sure that the cropped text doesn’t have any trailing space.
  • Try to maximize the number of words you can pack in your trimmed text.

Your function should look something like this:

def crop(text: str, limit: int) -> str:
    """Crops 'text' upto 'limit' characters."""

    # Crop the text.
    cropped_text = perform_crop()
    return cropped_text

For example, if text looks like this:

String interning in Python

· 5 min

I was reading the reference implementation of PEP-661: Sentinel Values and discovered an optimization technique known as String interning. Modern programming languages like Java, Python, PHP, Ruby, Julia, etc, performs string interning to make their string operations more performant.

String interning

String interning makes common string processing operations time and space-efficient by caching them. Instead of creating a new copy of string every time, this optimization method dictates to keep just one copy of string for every appropriate immutable distinct value and use the pointer reference wherever referred.