我正在尝试修补 __new__
一个类的方法,它没有按我预期的那样工作。
from contextlib import contextmanager
class A:
def __init__(self, arg):
print('A init', arg)
@contextmanager
def patch_a():
new = A.__new__
def fake_new(cls, *args, **kwargs):
print('call fake_new')
return new(cls, *args, **kwargs)
# here I get error: TypeError: object.__new__() takes exactly one argument (the type to instantiate)
A.__new__ = fake_new
try:
yield
finally:
A.__new__ = new
if __name__ == '__main__':
A('foo')
with patch_a():
A('bar')
A('baz')
我期望以下输出:A init foo
call fake_new
A init bar
A init baz
但是在call fake_new
之后我收到错误消息(请参阅代码中的注释)。对我来说 好像我只是装饰了一个
__new__
方法并传播所有参数不变。它不起作用,原因对我来说是模糊的。
我也可以写
return new(cls)
并调用 A('bar')
工作正常。但随后 A('baz')
休息。有人可以解释发生了什么吗?
Python 版本是 3.8
最佳答案
您遇到了 Python 对象实例化的一个复杂部分 - 在该部分中,该语言选择了一种允许创建自定义的设计 __init__
带参数的方法,不用碰__new__
.
但是,在类层次结构的基础中,object
, 两者 __new__
和 __init__
每个取一个参数。
IIRC,它是这样的:如果你的类(class)有一个自定义 __init__
而你没有碰__new__
并且类实例化的更多参数将传递给 __init__
和 __new__
,参数将从调用 do __new__
中剥离,因此您不必自定义它只是为了吞下您在 __init__
中消耗的参数.反之亦然:如果您的类(class)有自定义 __new__
带额外参数,没有自定义 __init__
,这些不会传递给 object.__init__
.
通过您的设计,Python 会看到一个自定义 __new__
并将传递给 __init__
的相同额外参数传递给它- 并通过使用 *args, **kw
,您将这些转发给 object.__new__
它接受单个参数 - 您会收到您向我们展示的错误。
解决方法是不将这些额外的参数传递给原始 __new__
方法 - 除非那里需要它们 - 因此您必须在启动对象时进行与 Python 类型相同的检查。
最重要的是一个有趣的惊喜:在使示例工作时,我发现即使 A.__new__
在修复补丁时被删除,cPython 的 type
仍然认为它是“触及”的实例化,并传递参数。
为了让你的代码工作,我需要留下一个永久 stub A.__new__
只会转发 cls
争论:
from contextlib import contextmanager
class A:
def __init__(self, arg):
print('A init', arg)
@contextmanager
def patch_a():
new = A.__new__
def fake_new(cls, *args, **kwargs):
print('call fake_new')
if new is object.__new__:
return new(cls)
return new(cls, *args, **kwargs)
# here I get error: TypeError: object.__new__() takes exactly one argument (the type to instantiate)
A.__new__ = fake_new
try:
yield
finally:
del A.__new__
if new is not object.__new__:
A.__new__ = new
else:
A.__new__ = lambda cls, *args, **kw: object.__new__(cls)
print(A.__new__)
if __name__ == '__main__':
A('foo')
with patch_a():
A('bar')
A('baz')
(我尝试检查原始 __new__
签名而不是 new is object.__new__
比较 - 无济于事:object.__new__
签名是 *args, **kwargs
- 可能是为了它永远不会在静态检查中失败)
关于Python 修补 __new__ 方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65360692/