英文:
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('A')
@click.register
def _(y:B, x:str, ):
print('B')
@click.register
def _(y:C, x:str):
print('C')
click(B(), "x")
#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('A')
@click.register(B)
def _(y, x):
print('B')
@click.register(C)
def _(y, x):
print('C')
x = 'str'
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"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)
# Output
#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>)
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)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论