Site Tools


python-singledispatch

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:

  • Base function with @singledispatch decorator
  • Called when no registered type matches
  • Usually raises NotImplementedError or provides fallback

Registering handlers:

  • @func.register(type): register handler for type
  • Can also use func.register(type, handler) syntax
  • Handler receives all original arguments

Type hierarchy:

  • Dispatch uses MRO (method resolution order)
  • If type hierarchy exists, most specific type is used
  • If multiple types in MRO are registered, most specific wins

Method dispatch:

  • @singledispatchmethod: for class methods
  • Dispatches on type of first argument after self
  • Useful for multi-type handling in classes

Limitations:

  • Only dispatches on first argument type
  • For multiple arguments, need multipledispatch library (third-party)
  • Not true overloading (Python doesn't support it natively)

Use cases:

  • Serialization/deserialization for different types
  • Visitor pattern without subclassing
  • Plugin system where plugins register handlers
  • Type-specific formatting, processing, or validation

vs if/elif chains:

  • Single dispatch: cleaner, more extensible, declarative
  • if/elif: simpler for two or three types, slightly faster
  • Single dispatch better for design; if/elif better for quick scripts

Registry inspection:

  • func.registry: dict of registered types and handlers
  • func.register(type, func): register if you have handler function
  • Useful for debugging, listing available handlers

Forward references:

  • Can't register type before class definition
  • Use string annotations or register after class defined
  • Or use func.register('TypeName') in Python 3.10+
python-singledispatch.md · Last modified: by 127.0.0.1