15

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

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' 试图调用__new__它从 'Parent' 继承的方法而不是它自己的__init__方法。

我必须在“孩子”中进行哪些更改才能获得预期的行为?

4

3 回答 3

13

首先,不认为最好的做法是__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
于 2012-05-28T19:03:05.697 回答
6

Child 不调用 Parent's__new__而不是自己的,而是在 before 之前__init__调用它自己的__new__(继承自 Parent)。 __init__

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

所以当你从一个类继承时你__new__所做的正是你在继承任何其他方法时所做的,这些方法在父级中具有与你想要的不同的签名。您需要重写__new__以接收 Child 的参数并Parent.__new__使用它期望的参数进行调用。然后您完全单独覆盖__init__(尽管使用相同的参数列表,并且同样需要__init__使用自己的预期参数列表调用 Parent )。

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

  1. 如果__new__返回一个现有对象(例如,因为 Parent 期望能够缓存它的实例),那么你可能会发现你的__init__在已经存在的对象上调用,如果您的对象应该具有可变状态,这可能会非常糟糕。但是,如果 Parent 期望能够做这种事情,它可能会期望对它的使用方式有一堆约束,并以任意打破这些约束的方式对其进行子类化(例如,将可变状态添加到一个类期望是不可变的,因此它可以缓存和回收其实例)几乎肯定不会工作,即使您可以正确初始化您的对象。如果这种事情正在发生,并且没有任何文档告诉您应该如何对 Parent 进行子类化,那么第三方可能并不打算让您成为 Parent 的子类,事情将会很困难为你。__new__而不是__init__,丑陋的。

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

于 2012-05-28T22:14:18.380 回答
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__,像这样:

.

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

我没有测试过。覆盖__new__很快就会让人感到困惑,所以如果可能的话最好避免它。

于 2012-05-28T18:51:53.133 回答