Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
220 changes: 220 additions & 0 deletions peps/pep-8108.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
PEP: 8108
Title: Adding coroutinedispatch to functools for Method Overloading
Author: Ricardo Robles <ricardo.r.f@hotmail.com>
Status: Draft
Type: Standards Track
Created: 16-Nov-2025

Abstract
========

This PEP proposes the addition of a ``coroutinedispatch`` class to the ``functools`` module in Python's standard library. The ``coroutinedispatch`` class enables method overloading based on argument types, supporting both synchronous and asynchronous methods. This feature simplifies the implementation of polymorphic behavior in Python, making it easier to write clean and maintainable code.

Motivation
==========

Python currently lacks built-in support for method overloading based on argument types. While developers can achieve similar functionality using conditional statements or external libraries, these approaches often result in verbose and less readable code. Adding a ``coroutinedispatch`` class to the standard library would:

- Provide a standardized and Pythonic way to implement method overloading.
- Enhance code readability and maintainability.
- Support both synchronous and asynchronous methods, addressing a common limitation in existing third-party solutions.

Rationale
=========

The ``coroutinedispatch`` class leverages Python's type hints and introspection capabilities to match method implementations based on argument types. This design aligns with Python's dynamic nature while encouraging the use of type annotations. By integrating this functionality into the ``functools`` module, we ensure consistency with other utility features for functional programming.

Specification
=============

The ``coroutinedispatch`` class will be implemented as follows:

- **Registration of Methods**: Developers can register multiple implementations of a method using the ``@coroutinedispatch`` decorator. The decorator inspects the argument types of the decorated function and stores the implementation accordingly.
- **Type Matching**: When the method is called, the ``coroutinedispatch`` class matches the provided arguments against the registered implementations. If no exact match is found, a ``TypeError`` is raised.
- **Support for Async Methods**: The ``coroutinedispatch`` class distinguishes between synchronous and asynchronous contexts, allowing developers to define separate implementations for each.

Example Usage
-------------

.. code-block:: python

import asyncio
from functools import coroutinedispatch

class Example:
@coroutinedispatch
def process(self, x: int) -> str:
return f"Processing integer: {x}"

@process.register
async def _(self, x: int) -> str:
await asyncio.sleep(0.1)
return f"Processing integer asynchronously: {x}"

@process.register
def _(self, x: str) -> str:
return f"Processing string: {x}"

@process.register
async def _(self, x: str) -> str:
await asyncio.sleep(0.1)
return f"Processing string asynchronously: {x}"

example = Example()
print(example.process(42)) # Processing integer: 42
print(example.process("hello")) # Processing string: hello

async def main():
print(await example.process(42)) # Processing integer asynchronously: 42
print(await example.process("hello")) # Processing string asynchronously: hello

asyncio.run(main())

Backwards Compatibility
=======================

This proposal introduces a new class to the ``functools`` module and does not modify any existing functionality. As such, it is fully backwards compatible.

Security Implications
=====================

The ``coroutinedispatch`` class relies on Python's type hints and introspection capabilities. It does not introduce any new security risks beyond those inherent to dynamic type checking.

How to Teach This
=================

The ``coroutinedispatch`` class should be documented in the Python standard library reference, with examples demonstrating its usage. Tutorials and guides on functional programming in Python can include sections on ``coroutinedispatch`` to showcase its benefits.

Reference Implementation
=========================

A reference implementation of the ``coroutinedispatch`` class is provided below:

.. code-block:: python

import inspect
import asyncio
from typing import Any, Callable, get_type_hints, get_origin
from functools import lru_cache


class coroutinedispatch:
def __init__(self, func: Callable):
self._sync_methods = {}
self._async_methods = {}
self._name = func.__name__

arg_types = self.get_arg_types(func)

if inspect.iscoroutinefunction(func):
self._async_methods[arg_types] = func
else:
self._sync_methods[arg_types] = func

def get_arg_types(self, func: Callable) -> tuple:
try:
hints = get_type_hints(func)
sig = inspect.signature(func)
params = list(sig.parameters.values())

if params and params[0].name in ("self", "cls"):
params = params[1:]

return tuple(hints.get(p.name, Any) for p in params)
except Exception:
return ()

@lru_cache(maxsize=128)
def match_types(self, provided_args: tuple, expected_types: tuple) -> bool:
if len(provided_args) != len(expected_types):
return False

for arg, expected in zip(provided_args, expected_types):
if expected is Any:
continue

origin = get_origin(expected)
if origin is not None:
expected = origin

if not isinstance(arg, expected):
return False

return True

@lru_cache(maxsize=128)
def _find_matching_method(self, is_async: bool, *args: Any) -> Callable:
"""Find the method that matches the argument types."""
methods = self._async_methods if is_async else self._sync_methods

# Search for exact match
for arg_types, method in methods.items():
if self.match_types(args, arg_types):
return method

# If no match, raise descriptive error
arg_type_names = tuple(type(arg).__name__ for arg in args)
context = "async" if is_async else "sync"
available = list(methods.keys())

raise TypeError(
f"No matching {context} method '{self._name}' found for "
f"arguments: {arg_type_names}. Available: {available}"
)

def register(self, func: Callable) -> Callable:
"""Register a new method overload."""
arg_types = self.get_arg_types(func)

if inspect.iscoroutinefunction(func):
self._async_methods[arg_types] = func
else:
self._sync_methods[arg_types] = func

return self

def __call__(self, *args: Any, **kwargs: Any) -> Any:
try:
asyncio.get_event_loop()
is_async_context = True
except RuntimeError:
is_async_context = False
pass

# Exclude 'self' from arguments if present
check_args = args[1:] if args and hasattr(args[0], self._name) else args

try:
method = self._find_matching_method(is_async_context, *check_args)
result = method(*args, **kwargs)

return result
except TypeError:
# If no async/sync method, try the other
try:
is_async_context = not is_async_context
method = self._find_matching_method(is_async_context, *check_args)
result = method(*args, **kwargs)

return result
except TypeError:
raise

def __get__(self, obj, objtype=None):
"""Support for bound methods"""
if obj is None:
return self

import functools

return functools.partial(self.__call__, obj)

Acknowledgements
================

Thanks to the Python community for their feedback and contributions to this proposal.

Copyright
=========

This document has been placed in the public domain.