Python 修补 __new__ 方法

标签 python python-3.x class

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

相关文章:

python - 图像中的 turtle 不会覆盖另一只 turtle 的印记

python-3.x - 导入Jupyter笔记本中另一个目录中的py文件

Java GUI 使用 setVisible()

C++ 访问类对象 vector 中的元素

python - 在没有 html 或原始消息的线程中提取消息

python - 如何解决使用 toc 将 Jupyter Notebook 下载为 HTML 的错误?

python - Pandas Reindex 来填充缺失日期,还是更好的方法来填充?

python - required_grad 在 PyTorch 中做什么? (不要求_grad)

python - ValueError : setting an array element with a sequence. 函数可以单独正常工作,但在另一个函数中使用时会导致错误

Python OOP问题,一个类的值没有被另一个类的函数更新