通过父初始值设定项

时间:2018-03-20 14:04:57

标签: python class subclass superclass

我有一个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函数进行初始化?

1 个答案:

答案 0 :(得分:5)

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(),这将返回一个完全一致的对象实例(记住,我们的{{1}是空的,所以对象没有任何属性)。不幸的是,我们无能为力。

一般来说,这样做可能是一个坏主意。我相信你现在可以告诉我,在尝试改变现有类的界面时,有许多陷阱需要注意。它很可能不值得麻烦,您应该坚持使用__init__创建实例。