Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can a function with keyword arguments be decorated with @multimethod? #131

Open
alzuse opened this issue Feb 12, 2025 · 3 comments
Open

Comments

@alzuse
Copy link

alzuse commented Feb 12, 2025

For example, I have the following code:

class Foo:
    @multimethod
    def call_n(self, name: str, nets: str):
        print(f"NETS")

    @call_n.register
    def call_n2(self, name: str, *, prefix: Union[str, int] = "",  suffix: Union[str, int] = ""):
        print("PS")
    
    @call_n.register
    def call_n3(self, name: str, func: types.FunctionType):
        print("FUNC:", func(name))


How can we distinguish between call_n and call_n2 when making a call?

@coady
Copy link
Owner

coady commented Feb 13, 2025

As is, called with a positional func works:

Foo().call_n('', lambda s: s)

Using @multidispatch instead of @multimethod also supports keyword arguments:

Foo().call_n('', func=lambda s: s)

@alzuse
Copy link
Author

alzuse commented Feb 15, 2025

As is, called with a positional func works:

Foo().call_n('', lambda s: s)
Using @multidispatch instead of @multimethod also supports keyword arguments:

Foo().call_n('', func=lambda s: s)

Could you please give an example of multidispatch? In the above example, I directly changed multimethod to multidispatch and then call,

from multimethod import multimethod, multidispatch
from typing import Union
import types

class Foo:
    # @multimethod
    @multidispatch
    def call_n(self, name: str, nets: str):
        print(f"NETS")

    @call_n.register
    def call_n2(self, name: str, *, prefix: Union[str, int] = "",  suffix: Union[str, int] = ""):
        print("PS")
    
    @call_n.register
    def call_n3(self, name: str, func: types.FunctionType):
        print("FUNC:", func(name)
ff = Foo()
ff.call_n("A", "A1")
ff.call_n("A", prefix="X", suffix="0")
ff.call_n("A", func=lambda x: x + "0"

but it reported errors as follows:

Traceback (most recent call last):
  File "/path/foo2.py", line 20, in <module>
    ff.call_n("A", "A1")
    ~~~~~~~~~^^^^^^^^^^^
  File ".../multimethod.py", line 410, in __call__
    func = self.dispatch(*params)
  File ".../multimethod.py", line 340, in dispatch
    return self[types]
           ~~~~^^^^^^^
  File ".../multimethod.py", line 335, in __missing__
    return self.setdefault(types, self.select(types, self.parents(types)))
                                  ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../multimethod.py", line 327, in select
    raise DispatchError(f"{self.__name__}: {len(keys)} methods found", types, keys)
multimethod.DispatchError: ('call_n: 0 methods found', (<class '__main__.Foo'>, <class 'str'>, <class 'str'>), set())

@coady
Copy link
Owner

coady commented Feb 15, 2025

Try it with a base/stub implementation:

class Foo:
    @multidispatch
    def call_n(self, name): ...

    @call_n.register
    def call_n1(self, name: str, nets: str):
        print(f"NETS")

    ...

multidispatch mimics functools.singledispatch by ignoring the annotations in the initial definition. I don't particularly like that behavior, but it was designed to be compatible.

And this is only relevant for keyword argument dispatching. Your original post with @multimethod works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants