Skip to content

Commit

Permalink
Multiple dispatch signatures.
Browse files Browse the repository at this point in the history
There is effectively no performance penalty if the first one matches. This allows supporting variable keyword arguments. Refs #87.
  • Loading branch information
coady committed May 2, 2024
1 parent 6608ac2 commit a8e9fdd
Show file tree
Hide file tree
Showing 3 changed files with 19 additions and 8 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## Unreleased
### Changed
* `multidispatch` supports variable keyword arguments

## [1.11.2](https://pypi.org/project/multimethod/1.11.2/) - 2024-02-27
### Fixed
Expand Down
22 changes: 15 additions & 7 deletions multimethod/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,19 +397,16 @@ class multidispatch(multimethod, dict[tuple[type, ...], Callable[..., RETURN]]):
Allows dispatching on keyword arguments based on the first function signature.
"""

signature: Optional[inspect.Signature]
signatures: dict[tuple, inspect.Signature]

def __new__(cls, func: Callable[..., RETURN]) -> "multidispatch[RETURN]":
return functools.update_wrapper(dict.__new__(cls), func) # type: ignore

def __init__(self, func: Callable[..., RETURN]) -> None:
self.pending = set()
self.generics = []
try:
self.signature = inspect.signature(func)
except ValueError:
self.signature = None
msg = "base implementation will eventually ignore annotations as `singledispatch does`"
self.signatures = {}
msg = "base implementation will eventually ignore annotations as `singledispatch` does"
with contextlib.suppress(NameError, AttributeError, TypeError):
hints = signature.from_hints(func)
if hints and all(map(issubclass, hints, hints)):
Expand All @@ -419,9 +416,20 @@ def __init__(self, func: Callable[..., RETURN]) -> None:
def __get__(self, instance, owner) -> Callable[..., RETURN]:
return self if instance is None else types.MethodType(self, instance) # type: ignore

def __setitem__(self, types: tuple, func: Callable):
super().__setitem__(types, func)
with contextlib.suppress(ValueError):
signature = inspect.signature(func)
self.signatures.setdefault(tuple(signature.parameters), signature)

def __call__(self, *args: Any, **kwargs: Any) -> RETURN:
"""Resolve and dispatch to best method."""
params = self.signature.bind(*args, **kwargs).args if (kwargs and self.signature) else args
params = args
if kwargs:
for signature in self.signatures.values(): # pragma: no branch
with contextlib.suppress(TypeError):
params = signature.bind(*args, **kwargs).args
break
func = self.dispatch(*params)
return func(*args, **kwargs)

Expand Down
3 changes: 2 additions & 1 deletion tests/test_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,10 @@ def _(arg: int):

@func.register
def _(arg: int, extra: float):
return int
return float

assert func(0) is func(arg=0) is int
assert func(0, 0.0) is func(arg=0, extra=0.0) is float
assert multidispatch(bool)(1)


Expand Down

0 comments on commit a8e9fdd

Please sign in to comment.