Giter Site home page Giter Site logo

fluent-python's Issues

第十二章-继承该如何是好?

第十二章 继承该如何是好


[我们]开始去推动继承**,使其成为了新手也可以构建以前只有原专家才可以设计的框架。

— 阿兰.凯《Smalltalk的早期历史》

本章涉及到了继承和子类化,这里有两处特别强调的针对Python的细节:

  • 子类化内建类型的陷阱
  • 多重继承与方法解析顺序

很多人认为多重继承带来的麻烦远大于其带来的好处。

然而,由于Java特别出色并具有广泛的影响力,这就意味着,在实际编程活动中很多程序员并没有见过多重继承。这就是为什么我们通过两个重要的项目来阐明多重继承的适应范围:Tkinter GUI套件,以及Django web 框架的原因。

我们从子类化内建类型的问题开始。余下的章节会用案例研究并学习多重继承,讨论在构建类的分层设计时会遇到的问题。

子类化内建类型是需要些技巧的

在Python2.2之前,子类化list或者dict这样的内建类型是不可能的。打那以后,Python虽然可以做到子类化内建类型,但是仍然要面对的重要警告是:内建的代码(由C语言重写)并不会调用被通过用户自定义类所覆盖的特殊方法。

对问题的准确描述都放在了PyPy文档,以及内建类型的子类化一节中的PyPy和CPython之间差异

正式地来说,Cpython对完全地重写内建类型的子类方法时是否明确地调用毫无规则可循。大略上,这些方法从来没有被其他的相同对象的内建方法所调用。例如,dict子类中的重写__getitem__()不会被get()这样的内建方法调用。

例子12-1阐明了此问题。

例子12-1。重写的__setitem__dict__init____update__方法所忽略。


>>> class DoppelDict(dict):
...     def __setitem__(self, key, value):
...         super(DoppelDict, self).__setitem__(key, [value] * 2)  # 1...
>>> dd = DoppelDict(one=1)  # 2
>>> dd
{'one': 1}
>>> dd['two'] = 2  # 3
>>> dd
{'one': 1, 'two': [2, 2]}
>>> dd.update(three=3)  # 4>
>> dd
{'three': 3, 'one': 1, 'two': [2, 2]}

1:存储时DoppelDict.__setitem__会使值重复(由于这个不好原因,因此必须有可见的效果)。它在委托到超类时才会正常运行。

2:继承自dict__init__方法,明确地忽略了重写的__setitem__'one'的值并没有重复。

3:[]运算符调用__setitem__,并如所希望的那样运行:'two'映射到了重复的值[2, 2]

4:dictupdate方法也没有使用我们定义的__setitem__:值'three'没有被重复。

该内建行为违反了面向对象的基本准则:方法的搜索应该总是从目标实例(self)的类开始,甚至是调用发生在以超类实现的方法之内部。在这样的悲观的情形下,

问题是在一个实例内部没有调用的限制,例如,不论self.get()是否调用self.__getitem__(),都会出现会被内建方法所调用其他类的方法被重写。下面是改编自PyPy文档的例子:

例子12-2。AnswerDict__getitem__dict.update所忽略。

>>> class AnswerDict(dict):
...     def __getitem__(self, key):  # 1
...
return 42
...
>>> ad = AnswerDict(a='foo')  # 2
>>> ad['a']  # 3
42
>>> d = {}
>>> d.update(ad)  # 4
>>> d['a']  # 5
'foo'
>>> d
{'a': 'foo'}

1:AnserDict.__getitem__总是返回42,不论键是什么。

2:ad是一个带有键值对('a', 'foo')AnswerDict

3:ad['a']如所期望的那样返回42。

4:d是一个普通使用ad更新的dict实例。

5:dict.update方法忽略了AnserDict.__getitem__

警告

直接地子类化类似dict或者list或者str这样的内建类型非常容易出错,因为大多数的内建方法会忽略用户所定义的重写方法。从被设计成易于扩展的collections模块的UserDictUserListUserString派生类,而不是子类化内建。

如果你子类化collections.UserDict而不是dict,那么例子12-1和例子12-2中的问题都会被该解决。见例子12-3。

例子12-3。DoppelDict2AnswerDict2一如所希望的运行,因为它们扩展的是UserDict而不是dict。


>>> import collections
>>>
>>> class DoppelDict2(collections.UserDict):
...     def __setitem__(self, key, value):
...         super().__setitem__(key, [value] * 2)
...
>>> dd = DoppelDict2(one=1)
>>> dd
{'one': [1, 1]}
>>> dd['two'] = 2
>>> dd
{'two': [2, 2], 'one': [1, 1]}
>>> dd.update(three=3)
>>> dd
{'two': [2, 2], 'three': [3, 3], 'one': [1, 1]}
>>>
>>> class AnswerDict2(collections.UserDict):
...     def __getitem__(self, key):
...         return 42
...
>>> ad = AnswerDict2(a='foo')
>>> ad['a']
42
>>> d = {}
>>> d.update(ad)
>>> d['a']
42
>>> d
{'a': 42}

为了估量内建的子类工作所要求体验,我重写了例子3-8中StrKeyDict类。继承自collections.UserDict的原始版本,由三种方法实现:__missing_____contains____setitem__

总结:本节所描述的问题仅应用于在C语言内的方法委托实现内建类型,而且仅对用户定义的派生自这些的类型的类有效果。如果你在Python中子类化类编程,比如,UserDict或者MutableMapping,你不会遇到麻烦的。

还有问题就是,有关继承,特别地的多重继承:Python如何确定哪一个属性应该使用,如果超类来自并行分支定义相同的名称的属性,答案在下面一节。

多重继承以及方法解析顺序

当不关联的祖先类实现相同名称的方法时,任何语言实现多重继承都需要解决潜在的命名冲突。这称做“钻石问题”,一如图表12-1和例子12-4所描述。

图片:
fluent python_c12_4

图表12-1.左边:UML类图表阐明了“钻石问题”。右边:虚线箭头为例子12-4描绘了Python MRO(方法解析顺序).
例子12-4. diamond.py:类A,B, C,和D构成了图表12-1中的图。

class A:
    def ping(self):
        print('ping:', self)


class B(A):
    def pong(self):
        print('pong:', self)


class C(A):
    def pong(self):
        print('PONG:', self)


class D(B, C):

    def ping(self):
        super().ping()
        print('post-ping:', self)

    def pingpong(self):
        self.ping()
        super().ping()
        self.pong()
        super().pong()
        C.pong(self)

注意类BC都实现了pong方法。唯一的不同是C.pong输出大写的单词PONG

如果你对实例D调用d.pong(),实际上哪一个pong方法会运行呢?对于C++程序员来说他们必须具有使用类名称调用方法,以解决这个模棱两可的问题。这样的问题在Python中也能够解决。看下例子12-5就知道了。

例子12-5.对类D的实例的pong方法调用的两种形式。

>>> from diamond import *
>>> d = D()
>>> d.pong()  #  1
pong: <diamond.D object at 0x10066c278>
>>> C.pong(d)  #  2
PONG: <diamond.D object at 0x10066c278>

1: 简单地调用d.pong导致B的运行。
2: 你可以总是直接地对调用超类的方法,传递实例作为明确的参数。

d.pong()这样的模棱两可的调用得以解决,因为Python在穿越继承图时,遵循一个特定的顺序。这个顺序就叫做MRO:方法解析顺序。类有一个被称为__mro__的属性,它拥有使用MRO顺序的超类的引用元组,即,当前的类的所有到object类的路径。拿类D来说明什么是__mro__(参见 图表12-1):

>>> D.__mro__
(<class 'diamond.D'>, <class 'diamond.B'>, <class 'diamond.C'>,
<class 'diamond.A'>, <class 'object'>)

推荐的调用超类的委托方法就是内建的super()函数,这样做是因为在Python3中较易使用,就像例子12-4中的类D的pingpong方法所阐述的那样。不过,有时候忽略MRO,对超类直接地调用方法也是也可以的,而且很方便。例如,D.ping方法可以这样写:

    def ping(self):
        A.ping(self)  # instead of super().ping()
        print('post-ping:', self)

注意,当调用直接调用一个类的实例时,你必须明确地传递self,因为你访问的是unbound method

不过,这是最安全的而且更未来化的使用super(),特别是在调用一个框架的方法时,或者任何不受你控制的类继承时。例子12-6演示了在调用方法时super()对MRO的遵循。

例子12-6。使用super()去调用ping(源码见例子12-4)。

真实世界中的多重继承

应对多重继承

1. 接口继承和接口实现之间的区别

继承

2. 使用ABC让接口更清晰

3. 为了代码重复利用而使用mixin

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.