Let’s talk about Python 3's fantastic single-dispatch generic function.

When all you have is a hammer.
Photo by DevVrat Jadon / Unsplash

Example

What’s a single-dispatch generic function?

It’s when you do this:

import json

from datetime import datetime
from decimal import Decimal
from functools import singledispatch

@singledispatch
def serialize(obj):
    """Raise error for attempts to serialize unimplemented types."""
    raise TypeError

@serialize.register
def _(obj: datetime):
    """Serialize datetimes to ISO format."""
    return obj.isoformat()

@serialize.register
def _(obj: Decimal):
    """Serialize decimals to string."""
    return f"Decimal({str(obj)})"

json.dumps(obj, default=serialize)

Instead of doing this:

import json

from datetime import datetime
from decimal import Decimal

def serialize(obj):
    """Serialize objects that the json library can't handle by default."""
    if isinstance(obj, datetime):
        result = obj.isoformat()
    elif isinstance(obj, Decimal):
        result = f"Decimal({str(obj)})"
    else:
        raise TypeError
    return result

json.dumps(obj, default=serialize)

Why use @singledispatch?

Because it enables easier unit-testing and typed code. (That's right, those type hints aren't just hints anymore!) Also, code reuse is simple -- just stack decorators:

@serialize.register(float)
@serialize.register(Decimal)
@serialize.register(int)
def _(obj):
    # Implement numeric-specific function here
    pass

There's nothing stopping you from using the traditional stack of if-elif-else statements if you prefer, but this is a nice alternative that's great to have on hand.

Background

The concept has been around Python for years. It was proposed in PEP 443 in 2013 and then implemented in Python 3.4 a year later. It essentially boils down to two parts:

  • A generic function. This is a function composed of several smaller functions that each implement the same operation for a different set of input types. When a generic function is called, the implementation that gets used is determined by the dispatch algorithm.
  • Single dispatch. This just means that the implementation is chosen by inspecting the type of only one of the inputs.

Polymorphism at its finest.