在Python中继承和重写__init__方法

147

我在阅读《深入 Python》这本书时,在类的章节中发现了以下示例:

class FileInfo(UserDict):
    "store file metadata"
    def __init__(self, filename=None):
        UserDict.__init__(self)
        self["name"] = filename

作者接着说,如果你想覆盖__init__方法,你必须明确调用父类的__init__方法并传入正确的参数。

  1. 那如果这个FileInfo类有多个祖先类呢?
    • 我是否需要明确调用所有祖先类的__init__方法?
  2. 此外,我是否需要对其他我想要覆盖的方法执行相同的操作?

3
请注意,重载是与覆盖不同的概念。 - Dana the Sane
6个回答

177

这本书在子类-父类调用方面有些过时。在子类化内置类方面也有点过时。

现在看起来是这样的:

class FileInfo(dict):
    """store file metadata"""
    def __init__(self, filename=None):
        super(FileInfo, self).__init__()
        self["name"] = filename

请注意以下内容:

  1. 我们可以直接继承内置类,如dictlisttuple等。

  2. super函数会处理追踪该类的超类并适当地调用其中的函数。


5
我应该寻找更好的书或教程吗? - liewl
2
在多重继承的情况下,super()会代表你追踪它们吗? - Dana
4
super()的意图是处理多继承。不过实际上,多继承仍然很容易出问题(参见http://fuhm.net/super-harmful/)。 - sth
2
是的,在多重继承和基类需要构造函数参数的情况下,你通常会发现自己手动调用构造函数。 - Torsten Marek
@S.Lott:Http/1.1 服务不可用 - 更正您的链接,我们将移除此问题。 - Mr_and_Mrs_D
显示剩余4条评论

22

在需要继承的每个类中,您可以运行一个循环,对于子类的初始化需要初始化的每个类进行初始化... 可以复制的示例可能更易于理解...

class Female_Grandparent:
    def __init__(self):
        self.grandma_name = 'Grandma'

class Male_Grandparent:
    def __init__(self):
        self.grandpa_name = 'Grandpa'

class Parent(Female_Grandparent, Male_Grandparent):
    def __init__(self):
        Female_Grandparent.__init__(self)
        Male_Grandparent.__init__(self)

        self.parent_name = 'Parent Class'

class Child(Parent):
    def __init__(self):
        Parent.__init__(self)
#---------------------------------------------------------------------------------------#
        for cls in Parent.__bases__: # This block grabs the classes of the child
             cls.__init__(self)      # class (which is named 'Parent' in this case), 
                                     # and iterates through them, initiating each one.
                                     # The result is that each parent, of each child,
                                     # is automatically handled upon initiation of the 
                                     # dependent class. WOOT WOOT! :D
#---------------------------------------------------------------------------------------#



g = Female_Grandparent()
print g.grandma_name

p = Parent()
print p.grandma_name

child = Child()

print child.grandma_name

4
Child.__init__ 中的 for 循环似乎是不必要的。当我从示例中删除它时,子类仍然会打印出 "Grandma"。难道祖先类的初始化不是由 Parent 类处理的吗? - Adam
4
我认为祖父母的初始化已经由父母的初始化处理了,不是吗? - johk95

18

你不一定需要调用父类的__init__方法,但通常你是想这么做的,因为在父类的初始化方法中会进行一些重要的初始化操作,这些操作是后续子类方法正常工作所必需的。

对于其他方法,这取决于你的意图。如果你只想添加一些基类的行为,你需要同时调用基类的方法以及你自己的代码。如果你想彻底改变行为,你可能不会调用基类的方法并直接在派生类中实现所有功能。


4
为了保证技术完整性,一些类(比如threading.Thread)如果你试图避免调用父类的__init__方法,则会抛出大量错误。 - David Berger
6
我觉得所有这些“你不需要调用基类构造函数”的谈论都非常令人恼火。在我所知的任何语言中,你都不需要调用它。它们所有的行为都会以相似的方式出错(或者不出错),即未初始化成员。建议不要初始化基类是错误的,因为它有很多问题。如果一个类现在不需要初始化,那么它将来也需要。构造函数是类结构/语言结构的一部分,并且应该正确使用。正确的用法是在派生类的构造函数中的某个时候调用它。所以请这样做。 - AndreasT
2
建议不初始化基类在很多方面都是错误的。没有人建议不初始化基类。请仔细阅读答案。这一切都关乎意图。1)如果您想保留基类的init逻辑,您不需要在派生类中覆盖init方法。2)如果您想扩展基类的init逻辑,则定义自己的init方法,然后从中调用基类的init方法。3)如果您想替换基类的init逻辑,则定义自己的init方法,同时不调用基类的方法。 - wombatonfire
@AndreasT retrobump,我知道,但有些语言强制要求你这样做,尤其是Java。例如,在Java中,语言会隐式调用super();,除非你显式地调用它或另一个构造函数,如果不能调用则会产生编译器错误。 - Leo Izen

4
如果FileInfo类有多个祖先类,则应该调用它们所有的__init__()函数。同样,对于析构函数__del__()也应该如此处理。这是一种清理资源的方式。

2

是的,你必须为每个父类调用__init__。如果你要覆盖两个父类中都存在的函数,也是一样的。


0
更加简单的是,在2023年,如果你不想手动输入每个参数在你的__init__()调用中,只需像这样使用*args**kwargs
class MyClass(ParentClass):
    def __init__(self, *args, **kwargs):
        # Processing before init call
        super().__init__(*args, **kwargs)
        # Processing after init call

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接