python-singledispatch
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:
- Base function with
@singledispatchdecorator - 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
multipledispatchlibrary (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 handlersfunc.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
