使用__new__覆盖子类中的__init__

时间:2016-01-14 04:39:40

标签: python object methods instance object-initializers

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

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__上运行内省表明新版本已经到位,但它并未作为对象创建的一部分进行调用。

2 个答案:

答案 0 :(得分: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,但添加它们不应该受到伤害;我没有测试它。)

答案 1 :(得分:0)

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

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

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