Python调用继承类方法super()

非常牛的参考文章:Python’s super() considered super

介绍#

众所周知,Python作为高级语言,既支持单继承,且支持多继承。在单继承中,如果子类想调用父类,可以使用super()。

官方解释:super()返回将方法调用委托给类型的父类或同级类的代理对象。 这对于访问已在类中重写的继承方法很有用。
原型:

Init signature: super(self, /, *args, **kwargs)
Docstring:
super() -> same as super(__class__, <first argument>)
super(type) -> unbound super object
super(type, obj) -> bound super object; requires isinstance(obj, type)
super(type, type2) -> bound super object; requires issubclass(type2, type)
Typical use to call a cooperative superclass method:
class C(B):
    def meth(self, arg):
        super().meth(arg)
This works for class methods too:
class C(B):
    @classmethod
    def cmeth(cls, arg):
        super().cmeth(arg)
Type:           type
Subclasses:

通常在两种情况下使用该函数:

  • 在具有单一继承的类层次结构中,super可以用于引用父类而无需显式命名它们,从而使代码更具可维护性。 这种用法与其他编程语言中super的用法非常相似。
  • 第二个用例是在动态执行环境中支持协作式多重继承。 这种方式是Python独有的,在静态编译语言或仅支持单继承的语言中找不到。 这使得在多个基类实现相同方法的情况下实现“菱形图”成为可能。 良好的设计要求该方法在每种情况下都具有相同的调用签名(因为调用的顺序是在运行时确定的,因为该顺序适合于类层次结构中的更改,并且因为该顺序可以包含在运行时之前未知的同级类 )。

还要注意,除零参数形式外,super()不限于在内部方法中使用。 两个参数形式准确地指定了参数并进行了适当的引用。 零参数形式仅在类定义内起作用,因为编译器会填写必要的详细信息以正确检索要定义的类,以及为常规方法访问当前实例。

一般用法-单继承#

单继承,子类继承builtin类:

import logging

class LoggingDict(dict):
    def __setitem__(self, key, value):
        logging.info('Setting %r to %r' % (key, value))
        super().__setitem__(key, value)

LoggingDict继承Python内置dict的所有功能,但是它扩展了__setitem__方法,以便在更新key时进行日志输入。 输入日志后,该方法使用super()委派工作以实际更新具有键/值对的字典。
在super()引入Python之前,如果我们想调用父类中的方法,通常我们会这样写:dict.setitem(self, key, value)。这种硬编码的方式引入了难以维护的代码,比如当LoggingDict修改继承的父类为SomeOtherMapping时,调用父类的方法就会变为SomeOtherMapping.setitem(self, key, value)。
使用super()更好的原因是super是一个计算的间接引用。意味着实际委托的父类或继承类可以在类继承层次结构中动态确定。
间接的好处之一是我们不必按名称指定委托类。 如果编辑源代码以将基类切换到其他类时,则super()引用将自动跟随。
比如上面的代码示例换成其他类继承时,代码其他部分不需要改变:

class LoggingDict(SomeOtherMapping):            # new base class
    def __setitem__(self, key, value):
        logging.info('Setting %r to %r' % (key, value))
        super().__setitem__(key, value)         # no change needed

#进阶用法-多继承# 除了使代码更易于维护之外,间接引用( computed indirection)继承类还具有另一个主要优点,这可能是静态语言的人们可能不熟悉的。 由于间接引用( computed indirection)是在运行时得到的,因此我们可以自由地影响间接引用的计算,以便间接指向其他类。 计算取决于调用super的类和当前类的祖先树。 第一个组件是调用super的类,由该类的源代码确定。 在我们的示例中,在LoggingDict .__ setitem__方法中调用了super()。 该部分是固定的。 第二个也是更有趣的部分是变量(我们可以使用祖先树来创建新的子类)。 如下所示:
from collections import OrderedDict

class LoggingOD(LoggingDict, OrderedDict):
    pass

>>> printLoggingOD.__mro__)
(<class '__main__.LoggingOD'>, <class '__main__.LoggingDict'>, <class 'collections.OrderedDict'>, <class 'dict'>, <class 'object'>)

类的__mro__属性:此属性是在方法解析期间寻找基类时要考虑的类的元组。举例来说,就是LoggingOD的实例在调用某些方法时的查找顺序。mro全称为Method Resolution Order,使用的算法为著名的C3算法,详情可见:The Python 2.3 Method Resolution Order
仔细考虑下上面的代码,我们没有更改LoggingDict的源代码。 相反,我们建立了一个子类LoggingOD,其唯一逻辑是继承两个现有类并控制它们的搜索顺序。


###Search Order### 如果我们的目标是根据自己的喜好创建带有MRO的子类,则我们需要知道如何计算它。 基础很简单。 序列包括该类,其基类以及这些基类的基类,依此类推,直到到达object(object是所有类的根类)为止。 该顺序是有序的,以便一个类始终出现在其父级之前,并且如果有多个父级,则它们与基类的元组保持相同的顺序。 The MRO shown above is the one order that follows from those constraints: - LoggingOD precedes its parents, LoggingDict and OrderedDict - LoggingDict precedes OrderedDict because LoggingOD.__bases__ is (LoggingDict, OrderedDict) - LoggingDict precedes its parent which is dict - OrderedDict precedes its parent which is dict - dict precedes its parent which is object

解决这些约束的过程称为线性化。 关于此主题有很多不错的文章,但是要创建我们喜欢的带有MRO的子类,我们只需要知道两个约束:子类先于父类,并且基于__bases__中出现的顺序。

实用建议###

super()负责将方法调用委派给实例的祖先树中的某个类。 为了使可重排序的方法调用起作用,需要协同设计类。 这提出了三个容易解决的实际问题:

  • 被super()调用的方法必须存在
  • 调用者和被调用者必须有相同的方法参数签名
  • 并且该方法的每次出现都需要使用super()

1). 首先,让我们看一下获取调用者参数以匹配被调用方法签名的策略。 这比事先已知被调用的传统方法调用更具挑战性。 使用super(),在编写类时不知道被调用方(因为稍后编写的子类可能会将新的类引入MRO中)。
一种方法是坚持使用位置参数的固定签名。 这对于__setitem__之类的方法具有很好的效果,该方法具有两个参数(一个key和一个value)的固定签名。 LoggingDict示例中显示了此实现,其中__setitem__在LoggingDict和dict中具有相同的签名。
一种更灵活的方法是使祖先树中的每个方法都经过协作设计,以接受关键字参数和关键字参数字典,以删除所需的任何参数,并使用**kwds转发其余参数,最终使字典在链中进行最后的调用时为空。
每一次调用都剥离其所需的关键字参数,以便最终的空dict可以发送到根本不需要参数的方法(例如,object .__ init__希望无任何参数(self除外)):

class Shape:
    def __init__(self, shapename, **kwds):
        self.shapename = shapename
        super().__init__(**kwds)


class ColoredShape(Shape):
    def __init__(self, color, **kwds):
        self.color = color
        super().__init__(**kwds)

cs = ColoredShape(color='red', shapename='circle')
>>> print(cs.__dict__)
{'color': 'red', 'shapename': 'circle'}

2). 在研究了使调用者/被调用者参数模式匹配的策略之后,现在让我们看一下如何确保目标方法存在。
上面的示例显示了最简单的情况。 我们知道object具有_init_方法,并且object始终是MRO链中的最后一个类,因此对super(). init__的任何调用序列都以对object .__ init__方法的调用结尾。 换句话说,我们保证super()调用的对象是存在的,并且不会因AttributeError而失败。
对于object没有感兴趣的方法的情况(例如draw()方法),我们需要编写一个root类,该root类必须保证在object之前被调用。 root类的职责只是吃掉方法调用,而无需使用super()进行转发调用。
Root.draw也可以使用断言进行defensive programming,以确保它不会在链的后面掩盖其他一些draw()方法。 如果子类错误地包含了具有draw()方法但不继承自Root的类,则可能会发生这种情况:

class Root:
    def draw(self):
        # the delegation chain stops here
        assert not hasattr(super(), 'draw')


class Shape(Root):
    def __init__(self, shapename, **kwds):
        self.shapename = shapename
        super().__init__(**kwds)

    def draw(self):
        print('Drawing.  Setting shape to:', self.shapename)
        super().draw()


class ColoredShape(Shape):
    def __init__(self, color, **kwds):
        self.color = color
        super().__init__(**kwds)

    def draw(self):
        print('Drawing.  Setting color to:', self.color)
        super().draw()


>>> cs = ColoredShape(color='blue', shapename='square')
>>> cs.draw()
Drawing. Setting color to: blue
Drawing. Setting shape to: square

如果子类希望将其他类注入MRO,则这些其他类也需要从Root继承,确保调用draw()的路径如果在Root.draw处终止,就无法到达object。。 应该清楚地记录下来,以便编写新的协同类的人知道从Root继承子类。 该限制与Python自己的要求(所有新exceptions必须继承自BaseException)没有太大不同。

3). 上面显示的技术确保super()调用已知存在的方法,并且签名正确。 但是,我们仍然依靠在每个步骤都调用super()来使委托链不中断。 如果我们要设计协作类,这很容易实现–只需向链中的每个方法添加一个super()调用即可。
上面列出的三种技术提供了设计可以由子类组成或重新排序的协作类的方法。

非协作类纳入协作类###

有时,子类可能希望与非专为该类设计的第三方类一起使用协作式多重继承技术(也许其感兴趣的方法未使用super()或该类未从root类继承)。 通过创建按规则的适配器类(adapter class),可以轻松地纠正这种情况。

class Moveable:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def draw(self):
        print('Drawing at position:', self.x, self.y)

例如,上面Moveable类不进行super()调用,并且具有与object .__ init__不兼容的__init __()方法签名,并且不继承自Root。
如果要在我们的协作式类ColoredShape层次结构中使用Moveable,则需要使用必需的super()调用来创建适配器:

class MoveableAdapter(Root):
    def __init__(self, x, y, **kwds):
        self.moveable = Moveable(x, y)
        super().__init__(**kwds)

    def draw(self):
        self.moveable.draw()
        super().draw()


class MoveableColoredShape(ColoredShape, MoveableAdapter):
    pass


>>> print(MoveableColoredShape.__mro__)
(<class '__main__.MoveableColoredShape'>, <class '__main__.ColoredShape'>, <class '__main__.Shape'>, <class '__main__.MoveableAdapter'>, <class '__main__.Root'>, <class 'object'>)
>>> mcs = MoveableColoredShape(color='red', shapename='triangle', x=10, y=20)
>>> mc.draw()
Drawing. Setting color to: red
Drawing. Setting shape to: triangle
Drawing at position: 10 20

#完整示例# 在Python 2.7和3.2中,collections模块同时具有Counter类和OrderedDict类。 这些类很容易组成一个OrderedCounter:
from collections import Counter, OrderedDict

class OrderedCounter(Counter, OrderedDict):
     'Counter that remembers the order elements are first seen'
     def __repr__(self):
         return '%s(%r)' % (self.__class__.__name__,
                            OrderedDict(self))
     def __reduce__(self):
         return self.__class__, (OrderedDict(self),)

>>> oc = OrderedCounter('abracadabra')
>>> OrderedCounter(OrderedDict([('a', 5), ('b', 2), ('r', 2), ('c', 1), ('d', 1)]))
OrderedCounter(OrderedDict([('a', 5), ('b', 2), ('r', 2), ('c', 1), ('d', 1)]))
posted @ 2019-12-13 16:16  Jeffrey_Yang  阅读(3157)  评论(0编辑  收藏  举报