如何从具有__new__方法的超类中正确继承?

时间:2012-05-28 18:28:00

标签: python inheritance

假设我们有一个类'Parent',由于某种原因定义了__new__,并且继承了它的类'Child'。 (在我的情况下,我试图继承我无法修改的第三方课程)

class Parent:
    def __new__(cls, arg):
        # ... something important is done here with arg

我的尝试是:

class Child(Parent):
    def __init__(self, myArg, argForSuperclass):
         Parent.__new__(argForSuperclass)
         self.field = myArg

但是

p = Parent("argForSuperclass")

按预期工作

c = Child("myArg", "argForSuperclass")

失败,因为'Child'尝试调用它从'Parent'而不是自己的__new__方法继承的__init__方法。

我必须更改“儿童”才能获得预期的行为?

3 个答案:

答案 0 :(得分:9)

首先,完全覆盖__new__以避免这些问题并不是最佳做法......但我知道,这不是你的错。对于此类情况,覆盖__new__的最佳做法是使其接受可选参数...

class Parent(object):
    def __new__(cls, value, *args, **kwargs):
        print 'my value is', value
        return object.__new__(cls, *args, **kwargs)

...所以孩子们可以得到自己的:

class Child(Parent):
    def __init__(self, for_parent, my_stuff):
        self.my_stuff = my_stuff

然后,它会起作用:

>>> c = Child(2, "Child name is Juju")
my value is 2
>>> c.my_stuff
'Child name is Juju'

但是,您父类的作者并不是那么明智并且给了您这个问题:

class Parent(object):
    def __new__(cls, value):
        print 'my value is', value
        return object.__new__(cls)

在这种情况下,只需覆盖子项中的__new__,使其接受可选参数,并在那里调用父项__new__

class Child(Parent):
    def __new__(cls, value, *args, **kwargs):
        return Parent.__new__(cls, value)
    def __init__(self, for_parent, my_stuff):
        self.my_stuff = my_stuff

答案 1 :(得分:4)

Child不会调用Parent __new__而不是自己的__init__,它会在 {{1}之前调用它自己的__new__(继承自Parent) }。

您需要了解这些方法的用途以及Python何时调用它们。调用__init__来提出实例对象,然后调用__new__来初始化该实例的属性。 Python会将相同的参数传递给两个方法:传递给类的参数以开始进程。

因此,当您从具有__init__的类继承时所执行的操作正是您继承父级中具有与您希望的签名不同的任何其他方法时所执行的操作。您需要覆盖__new__以接收Child的参数,并使用它期望的参数调用__new__。然后你完全单独覆盖Parent.__new__(虽然使用相同的参数列表,同样需要使用自己的预期参数列表调用Parent __init__)。

然而,有两个潜在的困难可能妨碍您,因为__init__用于自定义获取新实例的过程。没有理由必须实际创建一个 new 实例(它可以找到一个已存在的实例),实际上它甚至不必返回该类的实例。这可能会导致以下问题:

  1. 如果__new__返回现有对象(例如,因为Parent希望能够缓存其实例),那么您可能会发现已在现有对象上调用__new__如果你的对象应该具有可变状态,那就非常糟糕。但是,如果Parent希望能够做到这一点,它可能会期望对它的使用方式产生一系列限制,并以任意方式对这些约束进行子类化(例如将可变状态添加到类中期望是不可变的,所以它可以缓存和回收它的实例)几乎肯定不会工作,即使你可以正确初始化你的对象。如果这种事情正在发生,并且没有任何文档告诉你应该做什么来继承Parent,那么第三方可能并不打算你继承Parent,而事情将会很困难为了你。你最好的选择可能是尝试将你的所有初始化都移到__init__而不是__new__,这很丑陋。

  2. 如果类__init__方法实际返回类的实例,则仅 Python 调用__init__方法。如果Parent使用其__new__方法作为某种“工厂函数”来返回其他类的对象,那么以简单的方式进行子类化很可能会失败,除非你需要做的就是改变“工厂”的方式“工作。

答案 2 :(得分:1)

据我了解,当你致电Child("myarg", "otherarg")时,实际上意味着这样的事情:

c = Child.__new__(Child, "myarg", "otherarg")
if isinstance(c, Child):
    c.__init__("myarg", "otherarg")

你可以:

  1. 编写替代构造函数,如Child.create_with_extra_arg("myarg", "otherarg"),在执行其他任何需要之前实例化Child("otherarg")

  2. 覆盖Child.__new__,如下所示:

  3. def __new__(cls, myarg, argforsuperclass):
        c = Parent.__new__(cls, argforsuperclass)
        c.field = myarg
        return c
    

    我没有测试过。覆盖__new__可能会很快变得混乱,因此如果可能,最好避免使用它。