# 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. ```python # 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+