Rug*_*nar 7 python class python-3.x
我正在尝试修补__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')
Run Code Online (Sandbox Code Playgroud)
我期望以下输出:
A init foo
call fake_new
A init bar
A init baz
Run Code Online (Sandbox Code Playgroud)
但是在call fake_new我收到错误之后(请参阅代码中的注释)。对我来说,我似乎只是装饰一个__new__方法并传播所有参数不变。它不起作用,而且原因对我来说很模糊。
我也可以写return new(cls)和打电话,A('bar')效果很好。但随后就A('baz')断了。
有人能解释一下发生了什么事吗?
Python版本是3.8
您遇到了 Python 对象实例化的一个复杂部分 - 该语言选择了一种设计,允许人们创建__init__带有参数的自定义方法,而无需接触__new__.
然而,在类层次结构的基础中,object和__new__均__init__采用一个参数。
IIRC,它是这样的:如果你的类有一个自定义__init__并且你没有接触__new__,并且类实例化有更多的任何参数将传递给__init__和__new__,这些参数将从调用 do 中删除__new__,所以你不需要不必仅仅为了吞下您在 中使用的参数而对其进行自定义__init__。反之亦然:如果您的类有一个__new__带有额外参数的自定义,并且没有自定义__init__,则这些不会传递给object.__init__.
通过您的设计,Python 会看到一个自定义 __new__并向其传递与传递给的相同的额外参数__init__- 并且通过使用*args, **kw,您将那些object.__new__接受单个参数的参数转发给 - 然后您会得到您向我们提供的错误。
解决方法是不要将这些额外的参数传递给原始__new__方法 - 除非那里需要它们 - 因此您必须在启动对象时进行与 Python 类型相同的检查。
最重要的是一个有趣的惊喜:在使示例工作时,我发现即使A.__new__在恢复补丁时
被删除type,它仍然被cPython的实例化视为“触及”,并且参数被传递。为了让您的代码正常工作,我需要留下一个永久存根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')
Run Code Online (Sandbox Code Playgroud)
(我尝试检查原始__new__签名而不是new is object.__new__比较 - 无济于事:object.__new__签名*args, **kwargs- 可能是为了使其在静态检查时永远不会失败)
| 归档时间: |
|
| 查看次数: |
1072 次 |
| 最近记录: |