如何使用 Python 的多方法(multimethod)与自定义类作为参数类型。

huangapple go评论66阅读模式
英文:

how to use Python multimethod with custom class as argument type

问题

我有以下类设置:

class A:
    pass

class B(A):
    pass

class C(A):
    pass

然后我有一个方法,该方法带有两个参数:

  • x -> 总是一个字符串
  • y -> 上述类之一(A、B或C)
    根据传递的第二个参数的类别,我们执行不同的操作。
@multimethod
def click(x: str, y: A=None):
    do_A_thing()

@multimethod
def click(x: str, y: B=None):
    do_B_thing()

@multimethod
def click(x: str, y: object=None):
    raise expeption

使用这个配置,即使y是类型A或B,也会始终引发异常。

我还尝试过使用 singledispatch,但我需要对第二个参数进行验证,而且我不能交换它们的位置。
我还尝试过使用 multipledispatch,但它与multimethod类似地不起作用。

英文:

I have the following classes setup:

class A:
    pass

class B(A):
    pass

class C(A):
    pass

Then I have a method that gets called with 2 arguments:

  • x -> always a string
  • y -> one of the classes above (A, B, or C)
    And we do something different depending on which class was passed as second argument.
@multimethod
def click(x: str, y: A=None):
    do_A_thing()

@multimethod
def click(x: str, y: B=None):
    do_B_thing()

@multimethod
def click(x: str, y: object=None):
    raise expeption

With this configuration, an exception is always raised even when y is of type A or B.

I also looked at using singledispatch, but I need the validation on the second argument and I can't switch their places.
I also tried using multipledispatch, but it didn't work similarly to multimethod.

答案1

得分: 2

请查看doc。进一步注意到分发发生在“第一个参数的类型上”。因此,您应该重新设计函数的签名。

以下是一个最小的工作示例,其中函数用类型进行了注释:

@functools.singledispatch
def click(y:A, x:str):
    print('A')

@click.register
def _(y:B, x:str, ):
    print('B')

@click.register
def _(y:C, x:str):
    print('C')

click(B(), "x")
# B

对于一个没有使用类型注释的函数,您需要显式地将适当的类型参数传递给装饰器本身:

@functools.singledispatch
def click(y, x):
    print('A')

@click.register(B)
def _(y, x):
    print('B')

@click.register(C)
def _(y, x):
    print('C')

x = 'str'
click(B(), x)
# B

要切换参数的顺序,只需定义一个名为arg_swapper的装饰器,具有这样的功能:

def arg_swapper(func):
    # 交换函数参数的顺序(假设它接受恰好两个参数!)
    def wrapper(func_arg1, func_arg2):
        return func(func_arg2, func_arg1)
    return wrapper


def do_A_thing(x, y): print(f"do_A_thing({x=},{y=})")
def do_B_thing(x, y): print(f"do_B_thing({x=},{y=})")
def do_C_thing(x, y): print(f"do_C_thing({x=},{y=})")


@functools.singledispatch
@arg_swapper
def click(x, y): do_A_thing(x, y)    

@click.register(B)
@arg_swapper
def _(x, y): do_B_thing(x, y)

@click.register(C)
@arg_swapper
def _(x, y): do_C_thing(x, y)

x = 'str'
click(B(), x)
click(A(), x)
click(C(), x)

# 输出
# do_B_thing(x='string',y=<__main__.B object at 0x7f8ef577bd10>)
# do_A_thing(x='string',y=<__main__.A object at 0x7f8ef577bd10>)
# do_C_thing(x='string',y=<__main__.C object at 0x7f8ef577bd10>)

请注意,您可以将装饰器堆叠在一起(如上所示),或者进行显式调用:

@click.register(C)
def _(x, y): arg_swapper(do_C_thing)(x, y)
英文:

Have a look to the doc. Notice further that the dispatch occurs "on the type of the first argument". So you should refactor the signature of the functions.

Here a minimal working example for a functions annotated with types

@functools.singledispatch
def click(y:A, x:str):
    print(&#39;A&#39;)

@click.register
def _(y:B, x:str, ):
    print(&#39;B&#39;)

@click.register
def _(y:C, x:str):
    print(&#39;C&#39;)

click(B(), &quot;x&quot;)
#B

and for a function which doesn’t use type annotations you need to pass the appropriate type argument explicitly to the decorator itself

@functools.singledispatch
def click(y, x):
    print(&#39;A&#39;)

@click.register(B)
def _(y, x):
    print(&#39;B&#39;)

@click.register(C)
def _(y, x):
    print(&#39;C&#39;)

x = &#39;str&#39;
click(B(), x)
#B

For switching the order of the argument it's enough to define a decorator, arg_swapper, with this functionality

def arg_swapper(func):
    # interchange the order of the args of a function (assuming it takes exactly two parameters!)
    def wrapper(func_arg1, func_arg2):
        return func(func_arg2, func_arg1)
    return wrapper


def do_A_thing(x, y): print(f&quot;do_A_thing({x=},{y=})&quot;)
def do_B_thing(x, y): print(f&quot;do_B_thing({x=},{y=})&quot;)
def do_C_thing(x, y): print(f&quot;do_C_thing({x=},{y=})&quot;)


@functools.singledispatch
@arg_swapper
def click(x, y): do_A_thing(x, y)    

@click.register(B)
@arg_swapper
def _(x, y): do_B_thing(x, y)

@click.register(C)
@arg_swapper
def _(x, y): do_C_thing(x, y)

x = &#39;str&#39;
click(B(), x)
click(A(), x)
click(C(), x)

# Output
#do_B_thing(x=&#39;string&#39;,y=&lt;__main__.B object at 0x7f8ef577bd10&gt;)
#do_A_thing(x=&#39;string&#39;,y=&lt;__main__.A object at 0x7f8ef577bd10&gt;)
#do_C_thing(x=&#39;string&#39;,y=&lt;__main__.C object at 0x7f8ef577bd10&gt;)

Notice that you either stack the decorators (as shown above) or do an explicit call

@click.register(C)
def _(x, y): arg_swapper(do_C_thing)(x, y)

huangapple
  • 本文由 发表于 2023年7月14日 00:02:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/76681362.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定