使用 __new__ 覆盖子类中的 __init__

Posted

技术标签:

【中文标题】使用 __new__ 覆盖子类中的 __init__【英文标题】:Using __new__ to override __init__ in subclass 【发布时间】:2016-04-19 07:43:12 【问题描述】:

我有兴趣使用__new__ 功能将代码注入到子类的__init__ 函数中。我从文档中了解到,python 将在__new__ 返回的实例上调用__init__。但是,我在从 __new__ 返回之前更改实例中 __init__ 的值的努力似乎不起作用。

class Parent(object):

    def __new__(cls, *args, **kwargs):
        new_object = super(Parent, cls).__new__(cls)
        user_init = new_object.__init__
        def __init__(self, *args, **kwargs):
            print("New __init__ called")
            user_init(self, *args, **kwargs)
            self.extra()
        print("Replacing __init__")
        setattr(new_object, '__init__', __init__)
        return new_object

    def extra(self):
        print("Extra called")

class Child(Parent):

    def __init__(self):
        print("Original __init__ called")
        super(Child, self).__init__()

c = Child()

上面的代码打印:

Replacing __init__
Original __init__ called

但我希望它会打印出来

Replacing __init__
New __init__ called
Original __init__ called
Extra called

为什么不呢?

我觉得 Python 正在调用 __init__ 的原始值,而不管我在 __new__ 中将其设置为什么。在c.__init__ 上运行自省显示新版本已到位,但尚未作为对象创建的一部分调用。

【问题讨论】:

你有什么问题? 【参考方案1】:

好吧,在调用__init__ 之前,新对象应该是空的。所以可能python,作为优化,不费心去查询对象,直接从类中获取__init__

因此,您必须自己修改子类的__init__。幸运的是 Python 有一个工具,元类。

在 Python 2 中,您可以通过设置特殊成员来设置元类:

class Parent(object):
    __metaclass__ = Meta
    ...

见Python2 documentation

在 Python 3 中,您通过父列表中的关键字属性设置元类,所以

class Parent(metaclass=Meta):
    ...

见Python3 documentation

元类是类实例的基类。它必须派生自type,并且在它的__new__ 中,它可以修改正在创建的类(我相信__init__ 也应该被调用,但是示例会覆盖__new__,所以我会用它) . __new__ 将与您所拥有的类似:

class Meta(type):
    def __new__(mcs, name, bases, namespace, **kwargs):
        new_cls = super(Meta, mcs).__new__(mcs, name, bases, namespace, **kwargs)
        user_init = new_cls.__init__
        def __init__(self, *args, **kwargs):
            print("New __init__ called")
            user_init(self, *args, **kwargs)
            self.extra()
        print("Replacing __init__")
        setattr(new_cls, '__init__', __init__)
        return new_cls

(使用 Python 3 示例,但 Python 2 中的签名似乎是相同的,只是没有 **kwargs,但添加它们应该不会有什么坏处;我没有测试它)。

【讨论】:

【参考方案2】:

我怀疑答案是__init__ 是一个特殊函数,在内部它被定义为一个类方法,因此不能通过在对象实例中重新分配它来替换。

在 Python 中,所有对象都由 C 中的 PyObject 表示,它有一个指向 PyTypeObject 的指针。这包含一个名为 tp_init 的成员,我相信它包含一个指向 __init__ 函数的指针。

另一个解决方案有效,因为我们正在修改类,而不是对象的实例。

【讨论】:

PyTypeObject 代表type 对象,不是所有对象,不是吗? 啊。通过 github 在源代码中寻找PyObject 并找到了PyTypeObjectPyObject 有一个 ob_type 方法确实指向 PyTypeObject 所以我认为我的推理仍然可以成立。这真的取决于如何调用__init__ 方法。 重写了我的答案以尝试简化。

以上是关于使用 __new__ 覆盖子类中的 __init__的主要内容,如果未能解决你的问题,请参考以下文章

007_Python中的__init__,__call__,__new__

_new_()与_init_()的区别

_new_()与_init_()的区别

继承类中的python __init__方法

Python中的__init__和__new__

python中的__init__和__new__的区别