英文原版地址点这里
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()
的使用有很多的文章,但是大部分都有一些问题,本文将通过以下方式改善这种状况
super()
构建类的建议补: 钻石问题具体了解点击这里.
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 遵循以下的限制:
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() 的工作就是将方法调用委托给祖先树中的某个类。要让可重排列的方法调用正常工作,我们需要对这个类进行联合的设计。这也显露出了三个易于解决的实际问题:
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')
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