1. 程式人生 > >python's super() considered super

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