【问题标题】:Initialize instance of subclass by something other than parent initializer用父初始化器以外的东西初始化子类的实例
【发布时间】:2018-03-20 14:04:57
【问题描述】:

我有一个 Python 类(它实际上不是我的,它来自一个包,所以我不想编辑这个)。最小的例子(不切实际):

import os

class SuperClass:
    def __init__(self):
        # Do something, for example
        self.a = 0
        pass

    def from_file(filename):
        # Do something with the file,
        # use it to create superclass
        # instance obj. For example...
        obj = SuperClass()
        obj.a = os.stat(filename).st_size
        return obj

现在我想创建这个超类的一个子类,它的初始化总是来自一个文件——也就是说,我想做类似的事情

class SubClass(SuperClass):
    def __init__(self, filename):
        self = SuperClass.from_file(filename)

这不起作用(我没想到会这样):我没有收到异常,但子类未正确初始化。例如,如果SuperClass 有一些实例值a,它总是被设置,那么做

new_subclass_instance = SubClass('/filepath/file')
print(new_subclass_instance.a)

会给AttributeError: type object 'SubClass' has no attribute 'a'

如何使用父类中的from_file 函数进行初始化?

【问题讨论】:

  • 请编辑您的问题并添加您得到的例外情况。您发布的代码也不起作用,您忘记了第一堂课中的def
  • @FrancescoMontesano 也不例外。他们基本上是在问如何使用from_file 方法作为子类的构造函数。
  • @FrancescoMontesano,正如所指出的,我没有直接得到异常,但我已经稍微更新了问题以显示故障所在。
  • @MeesdeVries:我看不出异常与其他异常之间的联系。你期待什么? a 未在代码中定义,因此您会收到错误消息。如果我尝试您的代码,我会在初始化SubClass 时得到:NameError: name 'obj' is not defined。另外:您确定from_file 是正确的并且您没有忘记装饰器吗?
  • @FrancescoMontesano,我发布的内容并不意味着 实际代码 只是为了说明。超类中的注释行是实际代码的替身。特别是,在该代码中,obj 将被构造,并且在这两种方法中,值a 都将被设置。如果可以使代码更清晰,我可以扩展代码。换句话说,关键是超类没有接受文件名的__init__() 方法,它只有一个类函数,它返回一个使用文件名中的数据初始化的实例。在子类中,我想从文件名中获得一个实际的__init__()

标签: python class subclass superclass


【解决方案1】:

Python 的__init__ 方法实际上不是一个构造函数,它只是一个初始化器。这就是为什么您不能在 __init__ 方法中实现此行为的原因 - 一旦调用了 __init__,您已经有一个多余的对象实例漂浮在周围。

负责实际创建类的新实例的方法是__new__,因此我们可以通过覆盖它来实现我们的目标。基本思想是这样写:

class SubClass(SuperClass):
    def __new__(cls, filename):
        return cls.from_file(filename)

    def __init__(self, *args, **kwargs):
        pass

空的__init__ 是必要的,因为python 会自动在从__new__ 返回的对象上调用它。如果我们省略空的__init__ 方法,python 最终会在对象上调用SuperClass.__init__,这将重置其所有属性。

但是,有一个问题:由于我们重写了__new__ 方法,使其需要filename 参数,所以from_file 方法可能不再有效。您还没有显示它的代码,但它可能会按照以下方式做一些事情:

@classmethod
def from_file(cls, filename):
    obj = cls()
    obj.filename = filename
    return obj

这里的问题是对cls() 的调用将失败:这会调用我们的__new__ 方法,该方法需要filename 参数,没有任何参数。我们最终会遇到像TypeError: __new__() missing 1 required positional argument: 'filename' 这样的异常。为了解决这个问题,我们可以将filename 参数设为可选:

class SubClass(SuperClass):
    def __new__(cls, filename=None):
        if filename is None:
            return super().__new__(cls)
        return cls.from_file(filename)

    def __init__(self, *args, **kwargs):
        pass

现在我们可以根据需要拨打obj = SubClass('/filepath/file')然而,也可以不带任何参数调用SubClass(如obj = SubClass()),这将返回一个完全统一的对象实例(请记住,我们的__init__是空的,所以对象将有没有任何属性)。不幸的是,我们对此无能为力。

一般来说,这样做可能是个坏主意。我相信您现在可以看出,在尝试像这样更改现有类的接口时,必须注意许多陷阱。这很可能不值得麻烦,您应该坚持使用SubClass.from_file 创建实例。

【讨论】:

  • 谢谢,这很有意义。如果我现在调用obj = SubClass('filepath/file'),我认为子类__init__ 仍然被调用,并且使用相同的参数(即文件路径)是否正确?如果是这样,如果我想做进一步的子类特定初始化,在子类的 __init__ 方法中这样做最有意义吗?
  • @MeesdeVries 是的,这绝对正确; __init__ 将使用与 __new__ 相同的参数调用。但请记住,from_file 方法将在没有任何参数的情况下实例化您的类,因此如果您打算实现__init__,则必须使其所有参数都是可选的。因此,如果您的 __init__ 需要参数,则在 __new__ 中进行初始化可能更容易,而将 __init__ 留空。
  • 我遇到了另一个问题,SuperClass 实际上有一个__init__,它确实接受一个参数,虽然不是一个文件名,并且在调用cls.from_file(filename) my new SubClass __init__ 在不应该调用的地方被调用。 (在这一点上,我已经完全准备好放弃,只是“艰难地”去做,使用正确的父类构造函数,只是手工做一些事情。也许我想做的事情不值得。)
  • @MeesdeVries 我有点困惑。 SuperClass__init__ 无关紧要,只要您在子类中正确地用空的 __init__ 覆盖它即可。在cls()cls.from_file(filename) 之后调用子类的__init__ 是预期且正确的;这就是为什么它是空的很重要。但是无论如何,是的,尝试像这样更改类的接口肯定是麻烦多于其价值。如您所见,有很多陷阱。
  • 看来如果不调用SuperClass__new__,结果类型不是SubClassobj = SubClass('/filepath/file') ; print(type(obj)) 结果是 SuperClass 而不是预期的 SubClass
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-05-29
  • 2013-04-14
  • 2014-08-21
  • 1970-01-01
  • 2021-08-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多