coady / multimethod

Multiple argument dispatching.
https://coady.github.io/multimethod
Other
277 stars 24 forks source link

Overloading with optional parameters #16

Closed fsantini-vs closed 3 years ago

fsantini-vs commented 4 years ago

Hi,

The following example produces an error: ` class A: @multimethod def m(self, c: int, ii: Optional[int] = None): print('1', c, ii)

@multimethod
def m(self, ff: float):
    print('2', ff)

obj = A() obj.m(20) `

when calling obj.m(20): ` File "/usr/local/lib/python3.8/dist-packages/multimethod/init.py", line 184, in call return self[tuple(map(self.get_type, args))](*args, **kwargs) File "/usr/local/lib/python3.8/dist-packages/multimethod/init.py", line 180, in missing raise DispatchError(msg, types, keys) multimethod.DispatchError: ('a: 0 methods found', (<class 'main.A'>, <class 'int'>), [])

Process finished with exit code 1 `

Is there a way around it?

coady commented 4 years ago

Supporting dispatching on defaults is difficult, but there is a workaround. Use overloading itself to specify defaults:

class A:
     @multimethod
     def m(self, c: int, ii: Optional[int]):
         print('1', c, ii)

     @multimethod
     def m(self, c: int):
         return self.m(c, None)
hzhangxyz commented 3 years ago

it seems not work for keyward argument?

coady commented 3 years ago

it seems not work for keyward argument?

Keywords arguments are allowed but don't participate in the dispatch. As with defaults, it would be inefficient to implement. Instead of comparing tuples to types, the dispatch would have to compute the equivalent of Signature.bind for each call. Which is what overload does.

hzhangxyz commented 3 years ago

Since you use return self[tuple(map(self.get_type, args))](*args, **kwargs) to detect which func to call, I think it is a good choice to ignore keyword only argment when define multimethod.

For example

@multimethod
def f(int a, int b, *, int c, int d):
    pass

will return multimethod {(<class int>, <class int>): <function>}, instead of {(<class int>, <class int>, <class int>, <class int>): <function>}

This behavior is what "don't participate in the dispatch"

Since multimethod support python>=3.6, keyworld only argument is already supported. so this won't break consistency


It seems this change work

diff --git a/multimethod/__init__.py b/multimethod/__init__.py
index 1a11644..eca952c 100644
--- a/multimethod/__init__.py
+++ b/multimethod/__init__.py
@@ -25,7 +25,7 @@ def get_types(func: Callable) -> tuple:
     annotations = dict(typing.get_type_hints(func))
     annotations.pop('return', None)
     params = inspect.signature(func).parameters
-    return tuple(annotations.pop(name, object) for name in params if annotations)
+    return tuple(annotations.pop(name, object) for name in params if annotations and params[name].kind != inspect.Parameter.KEYWORD_ONLY)

 class DispatchError(TypeError):

Maybe VAR_POSITIONAL and VAR_KEYWORD should also be ignore?


I create a pr(#19) for this

coady commented 3 years ago

Seems like I misunderstood this request. I still think it's infeasible to dispatch on default and keywords arguments. But if the goal is simply to allow - and ignore - annotations on default and keyword parameters, that sounds reasonable.