python's super() considered super

英文原版地址点这里

Python’s super() considered super!

If you aren’t wowed by Python’s super() builtin, chances are you don’t really know what it is capable of doing or how to use it effectively.
译:如果你并没有对python的super()感到惊艳,那你就错失了真正了解它的作用和如果高效使用的机会。

Much has been written about super() and much of that writing has been a failure. This article seeks to improve on the situation by:
译:关于super()的使用有很多的文章,但是大部分都有一些问题,本文将通过以下方式改善这种状况

  • providing practical use cases 译:提高可以作为练习的案例
  • giving a clear mental model of how it works 译:给出一个清晰的运行工作原理模型
  • showing the tradecraft for getting it to work every time 译:展示每次运行的过程要点
  • concrete advice for building classes that use super() 译:提出来使用super()构建类的建议
  • favoring real examples over abstract ABCD diamond diagrams.译:给出真实的案例,而不是相对抽象的ABCD的钻石问题

补: 钻石问题具体了解点击这里.

  • 简要说明:
    “钻石问题”是一种歧义本质上是多重继承问题,即当两B和C类都继承自A,D类继承自B和C时会产生以下歧义:如果存在A中B和C 重写的方法,D不覆盖它,那么D继承的方法版本是哪个:B的那个,还是C的那个?
  • 原理图如下:
    在这里插入图片描述

The examples for this post are available in both Python 2 syntax and Python 3 syntax.译:本文示例包含 Python 2 语法 及 Python 3 语法两种版本

Using Python 3 syntax, let’s start with a basic use case, a subclass for extending a method from one of the builtin classes:译:基于python3语法,以一个基础的使用示例开始,用于从已经构建的类中构建一个子类来拓展一个方法

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

This class has all the same capabilities as its parent, dict, but it extends the setitem method to make log entries whenever a key is updated. After making a log entry, the method uses super() to delegate the work for actually updating the dictionary with the key/value pair.
译:这个类拥有与它父类(字典)相同的所有功能,不过它扩展了 __setitem__方法,无论哪一个键被更新,该条目都会被记录下来。记录完更新的条目之后,该方法使用super() 将更新键值对的实际工作委托给它的父类。

Before super() was introduced, we would have hardwired the call with dict.setitem(self, key, value). However, super() is better because it is a computed indirect reference.
译:在介绍 super() 之前,我们可能会使用具体的类名来调用 dict.setitem(self, key, value).但是, super() 会更好一些,因为它是通过计算得到的非直接引用

One benefit of indirection is that we don’t have to specify the delegate class by name. If you edit the source code to switch the base class to some other mapping, the super() reference will automatically follow. You have a single source of truth:
译:非直接引用的好处之一是我们不必通过具体的类名来指定执行操作的对象。如果你修改源代码,将原来的基类变成别的类,那么 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

In addition to isolating changes, there is another major benefit to computed indirection, one that may not be familiar to people coming from static languages. Since the indirection is computed at runtime, we have the freedom to influence the calculation so that the indirection will point to some other class.
译:除了与外界相独立的改变之外,非直接引用还有一个主要的好处,而那些使用静态语言的人对此可能比较不熟悉。既然非直接引用是运行时才进行计算,那我们就可以自由地改变计算过程,让它指向其它类。

The calculation depends on both the class where super is called and on the instance’s tree of ancestors. The first component, the class where super is called, is determined by the source code for that class. In our example, super() is called in the _LoggingDict.__setitem___method. That component is fixed. The second and more interesting component is variable (we can create new subclasses with a rich tree of ancestors).
译:这个计算由调用 super 的类和它的祖先树共同决定。第一个要素,也就是调用 super 的类,是由实现这个类的源代码所决定。在我们的示例中, super() 是在 LoggingDict.setitem 方法中被调用。这个要素是固定的。第二个要素,也是更有趣的要素,就是变量(我们可以创建新的子类,让这个子类具有丰富的祖先树))。

Let’s use this to our advantage to construct a logging ordered dictionary without modifying our existing classes:
译:我们使用这个对我们有利的方法,来构建一个logging ordered dictionary,而不用修改已经存在的代码。

class LoggingOD(LoggingDict, collections.OrderedDict):
    pass

The ancestor tree for our new class is: LoggingOD,_ LoggingDict_,OrderedDict,_ dict_,_ object_. For our purposes, the important result is that OrderedDict was inserted after LoggingDict and before dict! This means that the super() call in LoggingDict.setitem now dispatches the key/value update to OrderedDict instead of dict.
我们构建的新类的祖先树是: LoggingOD, LoggingDict, OrderedDict, dict, object。对于我们的目标来说,重要的结果是 OrderedDict 被插入到 LoggingDict 之后,并且在 dict 之前。这意味着现在 LoggingDict.setitem 中的 super() 调用把更新键值对的工作交给了 OrderedDict 而不是 dict 。
Think about that for a moment. We did not alter the source code for LoggingDict. Instead we built a subclass whose only logic is to compose two existing classes and control their search order.
稍微思考一下这个结果。我们之前并没有替换掉 LoggingDict 的源代码。相反,我们创建了一个子类,它的唯一逻辑就是将两个已有的类结合起来,并控制它们的搜索顺序。


Search Order
搜索顺序

What I’ve been calling the search order or ancestor tree is officially known as the Method Resolution Order or MRO. It’s easy to view the MRO by printing the mro attribute:
我所说的搜索顺序或者祖先树,正式的名称是 方法解析顺序,简称 MRO。通过打印 mro 属性,我们很容易就能获取MRO。

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

If our goal is to create a subclass with an MRO to our liking, we need to know how it is calculated. The basics are simple. The sequence includes the class, its base classes, and the base classes of those bases and so on until reaching object which is the root class of all classes. The sequence is ordered so that a class always appears before its parents, and if there are multiple parents, they keep the same order as the tuple of base classes.
如果我们的目标是创建一个具有我们想要的MRO的子类,我们需要知道它是如何被计算出来的。基础部分很简单。这个序列包含了类本身,它的基类,以及基类的基类,一直到所有类的祖先类 object 。这个序列经过了排序,因此一个类总是出现在它的父类之前,如果有多个父类,它们保持与基类元组相同的顺序。
The MRO shown above is the one order that follows from those constraints:
上面展示的 MRO 遵循以下的限制:

  • 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
    LoggingOD 在它的父类 LoggingDict 和 OrderedDict 之前
    LoggingDict 在 OrderedDict 之前,因为 LoggingOD.base 的值为 (LoggingDict, OrderedDict)
    LoggingDict 在它的父类 dict 之前
    OrderedDict 在它的父类 dict 之前
    dict 在它的父类 object 之前

    The process of solving those constraints is known as linearization. There are a number of good papers on the subject, but to create subclasses with an MRO to our liking, we only need to know the two constraints: children precede their parents and the order of appearance in bases is respected.
    解决这些限制的过程被称为线性化, 关于这个话题有许多优秀的论文,但要创建具有我们想要的MRO的一个子类,我们只需要知道两条限制:子类在父类之前、出现的顺序遵从 base 中的顺序。

Practical Advice
实用的建议
super() is in the business of delegating method calls to some class in the instance’s ancestor tree. For reorderable method calls to work, the classes need to be designed cooperatively. This presents three easily solved practical issues:
super() 的工作就是将方法调用委托给祖先树中的某个类。要让可重排列的方法调用正常工作,我们需要对这个类进行联合的设计。这也显露出了三个易于解决的实际问题:

  • the method being called by super() needs to exist
  • the caller and callee need to have a matching argument signature
  • and every occurrence of the method needs to use super()
    被 super() 调用的方法必须存在
    调用者和被调用者需要具有相同的参数签名
    该方法的每次调用都需要使用 super()
  1. Let’s first look at strategies for getting the caller’s arguments to match the signature of the called method. This is a little more challenging than traditional method calls where the callee is known in advance. With super(), the callee is not known at the time a class is written (because a subclass written later may introduce new classes into the MRO).
    1)我们先来看看使调用者与被调用者的参数签名相匹配的策略。比起传统的方法调用(提前知道被调用者是谁),这会有一点点挑战性。使用 super()编写一个类时,我们并不知道被调用者是谁(因为之后编写的子类可能会在 MRO 中引入新的类)。
    One approach is to stick with a fixed signature using positional arguments. This works well with methods like setitem which have a fixed signature of two arguments, a key and a value. This technique is shown in the LoggingDict example where setitem has the same signature in both LoggingDict and dict.
    一种方式是使用固定的签名,也就是位置参数。像 setitem 这样的方法拥有两个参数的固定签名,一个键和一个值,这种情况下能够很好地工作。这个技术在 LoggingDict 的示例中展示过,其中 setitem 在 LoggingDict 和 dict 中拥有同样的参数签名。

A more flexible approach is to have every method in the ancestor tree cooperatively designed to accept keyword arguments and a keyword-arguments dictionary, to remove any arguments that it needs, and to forward the remaining arguments using kwds, eventually leaving the dictionary empty for the final call in the chain.
一种更加灵活的方式是将每一个祖先类中对应的方法都共同设计成接收关键字参数和一个关键字参数字典,将它需要的参数移除,并将剩余的参数通过 kwds 继续传递,最终会在最后的调用中剩下一个空字典.

Each level strips-off the keyword arguments that it needs so that the final empty dict can be sent to a method that expects no arguments at all (for example, object.init expects zero arguments):
每一层都移除它所需要的关键字参数,最后的空字典可以被传递给一个不需要任何参数的方法(例如: object.init 不需要任何参数)

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')
  1. Having looked at strategies for getting the caller/callee argument patterns to match, let’s now look at how to make sure the target method exists.
    2) 看完了使调用者和被调用者的参数模式相匹配的策略,我们现在来看看如何确保目标方法存在
    The above example shows the simplest case. We know that object has an init method and that object is always the last class in the MRO chain, so any sequence of calls to super().init is guaranteed to end with a call to object.init method. In other words, we’re guaranteed that the target of the super() call is guaranteed to exist and won’t fail with an AttributeError.
    上面的示例展示了最简单的情况。我们知道 object 有一个 init 方法,并且 object 永远是 MRO 链中的最后一个类,所以任何调用 super().init 的序列都会以调用 object.init 方法作为结尾。换句话说,我们能确保 super() 调用的目标肯定存在,一定不会发生 AttributeError 的错误。
    For cases where object doesn’t have the method of interest (a draw() method for example), we need to write a root class that is guaranteed to be called before object. The responsibility of the root class is simply to eat the method call without making a forwarding call using super().
    对于我们想要的方法在 object 中并不存在的情况(例如 draw() 方法),我们需要编写一个一定会在 object 之前被调用的根类(root class)。这个根类的作用是在 object 之前将该方法吞噬掉,避免 super() 的继续调用。

Root.draw can also employ defensive programming using an assertion to ensure it isn’t masking some other draw() method later in the chain. This could happen if a subclass erroneously incorporates a class that has a draw() method but doesn’t inherit from Root.:
Root.draw 还能够利用防御式编程,通过使用 assertion 语句来确保它没有屏蔽掉 MRO 链中的其它 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()

If subclasses want to inject other classes into the MRO, those other classes also need to inherit from Root so that no path for calling draw() can reach object without having been stopped by Root.draw. This should be clearly documented so that someone writing new cooperating classes will know to subclass from Root. This restriction is not much different than Python’s own requirement that all new exceptions must inherit from BaseException.
如果子类想要将其它类插入到 MRO 链中,那么那些被插入的类也需要继承 Root ,以确保任何途径下调用 draw() 方法都不会到达 object 类,而会被 Root.draw 所拦截而终止。这一点应该清楚地写到文档中,这样一来如果有人编写与之相关的类,就知道应该继承 Root类了。这一限制,与 Python 要求所有异常类都要继承 BaseException 没有多大区别。
3) The techniques shown above assure that super() calls a method that is known to exist and that the signature will be correct; however, we’re still relying on super() being called at each step so that the chain of delegation continues unbroken. This is easy to achieve if we’re designing the classes cooperatively – just add a super() call to every method in the chain.
3) 上面展示的技术假定了 super() 调用的是一个已知存在、并且参数签名正确的方法。然而,我们仍依赖于 super() 在每一步中都被调用,代表链得以继续不至于断裂。我们如果联合设计这些类,那么这一点很容易达到——只需要在链中的每一个方法中都添加一个 super() 调用。
The three techniques listed above provide the means to design cooperative classes that can be composed or reordered by subclasses.
上面列出的三种技术,提供了一些方式让我们设计出能够通过子类来组合或重排序的联合类。


How to Incorporate a Non-cooperative Class
如何合并一个非联合(Non-cooperative)类
Occasionally, a subclass may want to use cooperative multiple inheritance techniques with a third-party class that wasn’t designed for it (perhaps its method of interest doesn’t use super() or perhaps the class doesn’t inherit from the root class). This situation is easily remedied by creating an adapter class that plays by the rules.
偶然情况下,一个子类可能想要对一个并非给它设计的第三方类使用联合多继承技术(可能该第三方类的有关方法并没有使用 super() 或可能它并没有继承 Root 类)。这种情况可以通过创建一个符合规则的适配器类(adapter class)来轻松解决。

For example, the following Moveable class does not make super() calls, and it has an init() signature that is incompatible with object.init, and it does not inherit from Root:
例如,下面的 Moveable 类没有调用 super() ,并且它的 init() 与 object.init() 的签名不兼容,此外它还没有继承 Root :

class Moveable:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def draw(self):
        print('Drawing at position:', self.x, self.y)

If we want to use this class with our cooperatively designed ColoredShape hierarchy, we need to make an adapter with the requisite super() calls:
如果我们想要将该类与我们联合设计的 ColoredShape 分层结构(hierarchy)一起使用,我们需要创建一个适配器,包含必要的 super() 调用:

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

class MovableColoredShape(ColoredShape, MoveableAdapter):
    pass

MovableColoredShape(color='red', shapename='triangle',
                    x=10, y=20).draw()

Complete Example – Just for Fun
完整示例(只为乐趣)
In Python 2.7 and 3.2, the collections module has both a _Counter_class and an OrderedDict class. Those classes are easily composed to make an OrderedCounter:
在 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')


Notes and References
说明和引用
***** When subclassing a builtin such as dict(), it is often necessary to override or extend multiple methods at a time. In the above examples, the setitem extension isn’t used by other methods such as dict.update, so it may be necessary to extend those also. This requirement isn’t unique to super(); rather, it arises whenever builtins are subclassed.
当继承内置的数据类型如 dict() 来创建子类的时候,通常有必要同时重载或扩展多个方法。在上面的示例中,setitem 的扩展没有被其它方法如 dict.update 所使用,因此也可能有必要对那些方法进行扩展。这一要求并非是 super() 所特有的,相反,任何通过继承内置类型创建子类的情况都需要满足这个要求。

***** If a class relies on one parent class preceding another (for example, LoggingOD depends on LoggingDict coming before _OrderedDict_which comes before dict), it is easy to add assertions to validate and document the intended method resolution order:
如果一个类依赖于一个父类,而这个父类又依赖于另一个类(例如,LoggingOD 依赖于 LoggingDict,而后者出现在 OrderedDict 之前,最后才是 dict),那么很容易通过添加断言(assertions)来验证并记录预计的方法解析顺序(MRO):

position = LoggingOD.__mro__.index
assert position(LoggingDict) < position(OrderedDict)
assert position(OrderedDict) < position(dict)

译文参考自:https://blog.csdn.net/yezhenquan123/article/details/78990271

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值