Python 修补 __new__ 方法

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

jsb*_*eno 4

您遇到了 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- 可能是为了使其在静态检查时永远不会失败)

  • 啊,既然`A.__new__`已经被触及了,即使它是原来的`__new__`,它也被认为是“自定义”?伙计,这真是出乎意料 (2认同)