如何使用 singledispatchmethod 重载 __new__,并且为什么它不能按预期工作?
我想让我的 __new__
方法在某些情况下表现得不一样,所以我想把它分成几个重载函数,使用 singledispatchmethod
。但是这样做不行,重载的函数根本没有被调用。这是什么原因呢?
from functools import singledispatchmethod
class Foo:
@singledispatchmethod
def __new__(cls, arg1, **kwargs):
return "default call"
@__new__.register(int)
def _(cls, arg1, **kwargs):
return "called with int " + str(arg1)
print(Foo("hi"))
# default call
print(Foo(1))
# default call
我还尝试用 singledispatch
来实验,但也没有成功。
1 个回答
你可能会觉得 singledispatchmethod
只是一个版本的 singledispatch
,它是根据第二个参数来分发的,而不是第一个,但其实并不是这样。
实际上,它是作为一个类来写的,这个类实现了 描述符协议,用来定制属性的访问。当你访问一个被 singledispatchmethod
装饰的方法时,属性访问会返回一个闭包对象,这个闭包对象会根据传给它的第一个参数来进行分发。
举个例子,如果有一个名为 Example
的类,它有一个叫 sdm
的 singledispatchmethod
,那么 Example().sdm(1)
会根据数字 1 的类型来分发,但 Example.sdm(Example(), 1)
会根据 Example()
的类型来分发!
__new__
不是一个普通的方法。它应该是一个静态方法,或者至少在访问时表现得像一个静态方法。(通常情况下,type.__new__
会自动把 __new__
方法转换为静态方法,但这只在 __new__
是一个普通的 Python 函数对象时才会发生,而如前所述,singledispatchmethod
是作为一个自定义类实现的。)
具体来说,当你执行 Foo(1)
时,得到的 __new__
调用就像是 Foo.__new__(Foo, 1)
。它会在 Foo
上获取 __new__
属性,然后用 Foo
和 1
作为参数调用它找到的内容。
由于 singledispatchmethod
的分发方式,它是根据 Foo
的类型来分发,而不是根据 1
的类型。
通常,如果你想要 singledispatchmethod
有类似静态方法的行为,获取这种行为的方法是利用 singledispatchmethod
的一个特性,它可以包装其他装饰器,像这样:
# This doesn't do what you need.
@functools.singledispatchmethod
@staticmethod
def __new__(cls, arg1, **kwargs):
...
@__new__.register(int)
@staticmethod
def _(cls, arg1, **kwargs):
...
不过,这并不能解决我们要根据哪个参数进行分发的问题。
相反,你可以把 __new__
写成一个普通的方法,然后委托给一个 singledispatch
辅助函数,并重新排列辅助函数的参数,把你想要分发的参数放在前面:
import functools
class Foo:
def __new__(cls, arg1, **kwargs):
return _new_helper(arg1, cls, **kwargs)
@functools.singledispatch
def _new_helper(arg1, cls, **kwargs):
return "default call"
@_new_helper.register(int)
def _(arg1, cls, **kwargs):
return "called with int " + str(arg1)
或者,当然,你也可以完全放弃 singledispatch
的用法,自己手动处理分发:
import functools
class Foo:
def __new__(cls, arg1, **kwargs):
if isinstance(arg1, int):
return "called with int " + str(arg1)
else:
return "default call"