Table of Contents

Python single dispatch

Python single dispatch (from functools.singledispatch) provides function overloading based on the type of the first argument. A single function name dispatches to different implementations depending on what type is passed, making type-specific behavior declarative and extensible without large if/elif chains.

Use single dispatch to handle different types elegantly, or to allow plugins to register handlers for new types.

Example

This example shows function dispatch by type without conditional chains.

# run: python3 singledispatch.py
# description: single dispatch for type-based function overloading
 
from functools import singledispatch
 
@singledispatch
def process(arg):
    """Default implementation for unknown types."""
    raise TypeError(f"Cannot process {type(arg)}")
 
@process.register(int)
def _(value: int):
    return value * 2
 
@process.register(str)
def _(value: str):
    return value.upper()
 
@process.register(list)
def _(value: list):
    return sum(value)
 
@process.register(dict)
def _(value: dict):
    return len(value)
 
# Dispatch based on argument type
print(f"process(5) = {process(5)}")
print(f"process('hello') = {process('hello')}")
print(f"process([1, 2, 3]) = {process([1, 2, 3])}")
print(f"process({{'a': 1, 'b': 2}}) = {process({'a': 1, 'b': 2})}")
 
# Plugin system: register new types
class Custom:
    def __init__(self, value):
        self.value = value
 
@process.register(Custom)
def _(obj: Custom):
    return obj.value
 
print(f"process(Custom(42)) = {process(Custom(42))}")
 
# Check registered types
print(f"\nRegistered types: {process.registry}")
 
# Dispatch with inheritance
class Animal:
    pass
 
class Dog(Animal):
    pass
 
@singledispatch
def speak(animal):
    print(f"Unknown animal: {animal}")
 
@speak.register(Dog)
def _(dog):
    print("Woof!")
 
@speak.register(Animal)
def _(animal):
    print("Generic animal sound")
 
speak(Dog())
speak(Animal())
speak("not an animal")  # uses default
 
# Method dispatch (Python 3.8+)
from functools import singledispatchmethod
 
class Formatter:
    @singledispatchmethod
    def format(self, arg):
        raise TypeError(f"Cannot format {type(arg)}")
 
    @format.register(int)
    def _(self, value: int):
        return f"Integer: {value}"
 
    @format.register(str)
    def _(self, value: str):
        return f"String: {value}"
 
fmt = Formatter()
print(f"\nFormatter: {fmt.format(123)}")
print(f"Formatter: {fmt.format('hello')}")

Common patterns

Default implementation:

Registering handlers:

Type hierarchy:

Method dispatch:

Limitations:

Use cases:

vs if/elif chains:

Registry inspection:

Forward references: