gpt4 book ai didi

Python 修补 __new__ 方法

转载 作者:行者123 更新时间:2023-12-03 18:58:19 31 4
gpt4 key购买 nike

我正在尝试修补 __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/

31 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com