Skip to content

Commit a8e9fdd

Browse files
committed
Multiple dispatch signatures.
There is effectively no performance penalty if the first one matches. This allows supporting variable keyword arguments. Refs #87.
1 parent 6608ac2 commit a8e9fdd

File tree

3 files changed

+19
-8
lines changed

3 files changed

+19
-8
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
55

66
## Unreleased
7+
### Changed
8+
* `multidispatch` supports variable keyword arguments
79

810
## [1.11.2](https://pypi.org/project/multimethod/1.11.2/) - 2024-02-27
911
### Fixed

multimethod/__init__.py

+15-7
Original file line numberDiff line numberDiff line change
@@ -397,19 +397,16 @@ class multidispatch(multimethod, dict[tuple[type, ...], Callable[..., RETURN]]):
397397
Allows dispatching on keyword arguments based on the first function signature.
398398
"""
399399

400-
signature: Optional[inspect.Signature]
400+
signatures: dict[tuple, inspect.Signature]
401401

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

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

419+
def __setitem__(self, types: tuple, func: Callable):
420+
super().__setitem__(types, func)
421+
with contextlib.suppress(ValueError):
422+
signature = inspect.signature(func)
423+
self.signatures.setdefault(tuple(signature.parameters), signature)
424+
422425
def __call__(self, *args: Any, **kwargs: Any) -> RETURN:
423426
"""Resolve and dispatch to best method."""
424-
params = self.signature.bind(*args, **kwargs).args if (kwargs and self.signature) else args
427+
params = args
428+
if kwargs:
429+
for signature in self.signatures.values(): # pragma: no branch
430+
with contextlib.suppress(TypeError):
431+
params = signature.bind(*args, **kwargs).args
432+
break
425433
func = self.dispatch(*params)
426434
return func(*args, **kwargs)
427435

tests/test_dispatch.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,10 @@ def _(arg: int):
114114

115115
@func.register
116116
def _(arg: int, extra: float):
117-
return int
117+
return float
118118

119119
assert func(0) is func(arg=0) is int
120+
assert func(0, 0.0) is func(arg=0, extra=0.0) is float
120121
assert multidispatch(bool)(1)
121122

122123

0 commit comments

Comments
 (0)