首页 > 解决方案 > Python使用super与孙子继承父母和孩子

问题描述

[使用 Python3.6] 我有一个设计,其中孙子继承了父子(父子)。

class Parent:
    def aux_func(self):
        return "[parent aux]"

    def main_func(self):
        print("[parent main]" + self.aux_func())


class Child(Parent):
    def aux_func(self):
        return "[child aux]"

    def main_func(self):
        print("[child main]" + self.aux_func())


class Grandchild(Child, Parent):
    @classmethod
    def do_something(cls):
        g = Grandchild()
        g.main_func()
        super(Child, g).main_func()
        Parent.main_func(g)

Grandchild.do_something()

结果是——

[child main][child aux]
[parent main][child aux]
[parent main][child aux]

从 Parent 调用函数会导致 aux_func 从 Child 类解析。我试图通过 MRO 流程,但无法解释从不同类调用的函数。有人可以帮我吗

  1. 为什么会这样?
  2. 实现 [parent main][parent aux] 的解决方法是什么?

标签: pythonpython-3.6multiple-inheritancemethod-resolution-order

解决方案


你误解了什么super()super()不会改变self引用的类型。super(..., self).method()仍然会将该self引用传递给被调用的方法self,在所有三种情况下,Grandchild()实例也是如此。

这意味着在所有情况下,都self.aux_func()遵循正常的属性解析顺序,Grandchild()例如,self.aux_func()总是会找到Child.aux_func并调用它。

换句话说,唯一改变的查找是您在super()对象本身上查找的属性。如果您需要更多此类更改,则需要super() 再次使用,或者您需要为每个类赋予不同aux_func()的函数名称。一种方法是使方法类成为私有的。

后者可以通过在开头(但不是结尾)用两个下划线命名您的函数来完成。然后在编译时更改此类名称,以将类名注入到它所引用的所有位置:

class Parent:
    def __aux_func(self):
        # class private to Parent
        return "[parent aux]"

    def main_func(self):
        # any reference to __aux_func *in this class* will use
        # the Parent class-private version
        print("[parent main]" + self.__aux_func())


class Child(Parent):
    def __aux_func(self):
        # class private to Child
        return "[child aux]"

    def main_func(self):
        # any reference to __aux_func *in this class* will use
        # the Child class-private version
        print("[child main]" + self.__aux_func())

请参阅标识符文档的保留类

__*
类私有名称。此类别中的名称,当在类定义的上下文中使用时,会被重新编写以使用重整形式,以帮助避免基类和派生类的“私有”属性之间的名称冲突。

标识符(名称)部分

Private name mangling:当在类定义中以文本形式出现的标识符以两个或多个下划线字符开头并且不以两个或多个下划线结尾时,它被认为是该类的私有名称。在为私有名称生成代码之前,私有名称会转换为更长的形式。转换插入类名,删除前导下划线并在名称前面插入一个下划线。例如,__spam出现在名为的类中的标识符Ham将转换为_Ham__spam. 此转换与使用标识符的语法上下文无关。

通过使用类私有命名__aux_func,从定义的方法中对它的任何引用都Parent将查找和查找_Parent__aux_func,并且对同名的任何引用都Child将查找和查找_Child__aux_func。这两个名称是不同的,因此不会冲突:

>>> class Grandchild(Child, Parent):
...     @classmethod
...     def do_something(cls):
...         g = Grandchild()
...         g.main_func()
...         super(Child, g).main_func()
...         Parent.main_func(g)
...
>>> Grandchild.do_something()
[child main][child aux]
[parent main][parent aux]
[parent main][parent aux]

实现此目的的另一种方法是明确地使用不同的名称;说parent_aux_func()child_aux_func()。类私有名称实际上仅适用于旨在由第三方代码子类化的 API,而对子类可以使用的名称没有太多限制。


推荐阅读