【问题标题】:how do I properly inherit from a superclass that has a __new__ method?如何正确地从具有 __new__ 方法的超类继承?
【发布时间】:2012-06-03 01:49:45
【问题描述】:

假设我们有一个类 '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' 尝试调用它从 'Parent' 继承的 __new__ 方法,而不是它自己的 __init__ 方法。

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

【问题讨论】:

标签: python inheritance


【解决方案1】:

首先,完全覆盖__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

【讨论】:

  • 孩子实际上是在为父母的(编码)罪孽付出代价。
【解决方案2】:

子级不会调用父级的__new__ 而不是自己的__init__,而是调用自己的__new__(继承自父级)之前 __init__

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

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

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

    1234563您的对象应该具有可变状态。但是,如果 Parent 期望能够做这种事情,它可能会期望对它的使用方式有一堆约束,并以任意打破这些约束的方式对其进行子类化(例如,将可变状态添加到一个期望是不可变的,因此它可以缓存和回收其实例)几乎肯定不会工作,即使您可以正确初始化您的对象。如果这种事情正在发生,并且没有任何文档告诉您应该如何对 Parent 进行子类化,那么第三方可能并不打算让您成为 Parent 的子类,事情将会很困难为你。不过,您最好的选择可能是尝试将所有初始化移动到 __new__ 而不是 __init__,尽管如此丑陋。
  1. Python 在类的__new__ 方法实际上返回该类的实例时调用__init__ 方法。如果 Parent 使用它的__new__ 方法作为某种“工厂函数”来返回其他类的对象,那么以简单的方式进行子类化很可能会失败,除非您需要做的就是改变“工厂”的工作方式.

【讨论】:

    【解决方案3】:

    据我了解,当您拨打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__ 很快就会让人感到困惑,因此最好尽可能避免。

    【讨论】:

      猜你喜欢
      • 2020-11-13
      • 2011-10-06
      • 2017-11-12
      • 2023-03-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-04
      • 2012-02-04
      相关资源
      最近更新 更多