如下,我们已经有了一个从Contact类继承过来的Friend类

class ContactList(list):
def search(self, name):
'''Return all contacts that contain the search value
in their name.'''
matching_contacts = []
for contact in self:
if name in contact.name:
matching_contacts.append(contact)
return matching_contacts class Contact:
all_contacts = ContactList() def __init__(self, name, email):
self.name = name
self.email = email
Contact.all_contacts.append(self) class Friend(Contact):
'''通过super得到父类对象的实例,并且调用这个对象的__init__方法,
传递给它预期的参数,然后这个类做了自己的初始化,即设置phone属性'''
def __init__(self, name, email, phone):
super().__init__(name, email)
self.phone = phone

如果要给Friend类增加一个住址的方法,住址信息包括街道、城市、国家等。我们可以把这些字符串直接传递给Friend中的__init__方法,另外也可以把这些字符串先存放在一个元组或者字典里面,然后再把他作为单一的参数传递给__init__方法。

另一种方法就是,创建一个新的Address类来专门包括这些字符串,并且把这个类的一个实例传给Friend类的__init__方法。这样做的好处是在其他的如建筑、商业、组织中重用这个Address类。

class AddressHolder:
def __init__(self, street, city, state, code):
self.street = street
self.city = city
self.state = state
self.code = code

现在问题来了,在已经存在的从Contact类继承过来的Friend类中如何增加一个住址。

最好的方法是多重继承,但是这样会有两个父类的__init__方法需要被初始化,并且他们要通过不同的参数进行初始化,如何来做呢?让我们从一个天真的方法开始,对上述代码的Friend进行改写:

class Friend(Contact, AddressHolder):
def __init__(self, name, email, phone, street, city, state, code):
Contact.__init__(self, name, email)
AddressHolder.__init__(self, street, city, state, code)
self.phone = phone

上述从技术层面上是可以工作的,但是存在一些问题。

首先,如果我们忽略显式地调用初始化函数可能会导致一个超类未被初始化。在这里并不明显,但是在另一些场景会导致程序崩溃,比如把数据插入到一个未连接的数据库里。

第二,由于这些类的层次结果,可能会导致某个超类被调用多次。如下图所示。

从上图中,Friend中的__init__首先调用了Contact中的__init__,隐私初始化了object(所有类都继承于object)。Friend然后又调用AddressHolder的__init__,又一次隐式初始化了object超类,父类被创建了两次。在我们的这个情况下,它是无害的,但是在一些场景中,会带来灾难。(每一个方法的调用顺序可以通过__mro__修改,这里略)

再看如下的一个例子,我们有一个基类,该基类有一个call_me方法,有两个子类重写了这个方法,然后第3个类通过多重继承扩展了两个方法。这称为钻石继承。

从技术上来说,在python3的所有多重继承都是钻石继承,因为所有的类都从object继承,上图中的object.__init__同样是一个钻石问题。把上图转化成代码如下:

class BaseClass:
num_base_calls = 0
def call_me(self):
print("Calling method on Base Class")
self.num_base_calls += 1 class LeftSubclass(BaseClass):
num_left_calls = 0
def call_me(self):
BaseClass.call_me(self)
print("Calling method on Lef Subclass")
self.num_left_calls += 1 class RightSubclass(BaseClass):
num_right_calls = 0
def call_me(self):
BaseClass.call_me(self)
print("Calling method on Right Subclass")
self.num_right_calls += 1 class Subclass(LeftSubclass, RightSubclass):
num_sub_calls = 0
def call_me(self):
LeftSubclass.call_me(self)
RightSubclass.call_me(self)
print("Calling method on Subcalss")
self.num_sub_calls += 1

调用并得到如下输出:

$ python -i zuanshi.py
>>> s = Subclass()
>>> s.call_me()
Calling method on Base Class
Calling method on Lef Subclass
Calling method on Base Class
Calling method on Right Subclass
Calling method on Subcalss
>>> print(s.num_sub_calls, s.num_left_calls,
... s.num_right_calls, s.num_base_calls)
1 1 1 2

基类的call_me被调用了两次。但这不是我们想要的,如果在做实际的工作,这将导致非常严重的bug,如银行存款存了两次。

对于多重继承,我们只想调用“下一个”方法,而不是父类的方法。实际上,下一个方法可能不属于当前类或者当前类的父类或者祖先类。关键字super可以解决这个问题,如下是代码重写:

class BaseClass:
num_base_calls = 0
def call_me(self):
print("Calling method on Base Class")
self.num_base_calls += 1 class LeftSubclass(BaseClass):
num_left_calls = 0
def call_me(self):
super().call_me()
print("Calling method on Lef Subclass")
self.num_left_calls += 1 class RightSubclass(BaseClass):
num_right_calls = 0
def call_me(self):
super().call_me()
print("Calling method on Right Subclass")
self.num_right_calls += 1 class Subclass(LeftSubclass, RightSubclass):
num_sub_calls = 0
def call_me(self):
super().call_me()
print("Calling method on Subcalss")
self.num_sub_calls += 1

执行结果如下:

>>> s = Subclass()
>>> s.call_me()
Calling method on Base Class
Calling method on Right Subclass
Calling method on Lef Subclass
Calling method on Subcalss
>>> print(s.num_sub_calls, s.num_left_calls,
... s.num_right_calls, s.num_base_calls)
1 1 1 1

首先,Subclass的call_me方法调用了super().call_me(),其实就是引用了LeftSubclass.call_me()方法。然后LeftSubclass.call_me()调用了super().call_me(),但是这时super()引用了RightSubclass.call_me()。需要特别注意:super调用并不是调用LeftSubclass的超类(就是BaseClass)的方法。它是调用RightSubclass,虽然它不是LeftSubclass的父类!这就是下一个方法,而不是父类方法。RightSubclass然后调用BaseClass,并且通过super调用保证在类的层次结构中每一个方法都被执行一次。

参考:

1、《Python3 面向对象编程》 [加]Dusty Philips 著

python多重继承的钻石问题的更多相关文章

  1. C++_day8_ 多重继承、钻石继承和虚继承

    1.继承的复习 1.1 类型转换 编译器认为访问范围缩小是安全的. 1.2 子类的构造与析构 子类中对基类构造函数初始化只能写在初始化表里,不能写在函数体中. 阻断继承. 1.3 子类的拷贝构造与拷贝 ...

  2. python多重继承C3算法

    python多重继承的MRO算法选择: 经典方式.Python2.2 新式算法.Python2.3 新式算法(C3).Python 3中只保留了最后一种,即C3算法 C3算法的解析: 1.多继承UML ...

  3. 深入super,看Python如何解决钻石继承难题 【转】

    原文地址 http://www.cnblogs.com/testview/p/4651198.html 1.   Python的继承以及调用父类成员 python子类调用父类成员有2种方法,分别是普通 ...

  4. python 多重继承

    多重继承 除了从一个父类继承外,Python允许从多个父类继承,称为多重继承. 多重继承的继承链就不是一棵树了,它像这样: class A(object): def __init__(self, a) ...

  5. python多重继承:

    除了从一个父类继承外,Python允许从多个父类继承,称为多重继承. 多重继承的继承链就不是一棵树了,它像这样: class A(object): def __init__(self, a): pri ...

  6. 深入super,看Python如何解决钻石继承难题

    1.   Python的继承以及调用父类成员 python子类调用父类成员有2种方法,分别是普通方法和super方法 假设Base是基类 class Base(object): def __init_ ...

  7. python 多重继承 深度优先还是广度优先

    我们常说,python2 是深度优先,python3 是广度优先, 其实具体来说是 python2.2 及其以前是深度优先 python2.3及其以后就是广度优先了 python官网 讲解1 以及su ...

  8. python之路----钻石继承

    钻石继承 继承顺序 class A(object): def test(self): print('from A') class B(A): def test(self): print('from B ...

  9. (转载)深入super,看Python如何解决钻石继承难题

    1.   Python的继承以及调用父类成员 python子类调用父类成员有2种方法,分别是普通方法和super方法 假设Base是基类 class Base(object): def __init_ ...

随机推荐

  1. Nginx 减少磁盘读写次数

    L:133

  2. 了解AutoCAD对象层次结构 —— 3 ——数据库

    数据库的结构是什么样的?对象是如何存储在数据库中的?这些问题我们需要搞明白.在此我们可以借助工具ArxDbg或MgdDbg来查看数据库结构及其内容.下图就是利用MgdDbg工具查看到的内容,我们可以看 ...

  3. 我的POI代码库(持续更新)

    添加的maven依赖是 <poi.version>3.15</poi.version> ... <dependency> <groupId>org.ap ...

  4. 《App后台开发运维与架构实践》第2章 App后台基础技术

    2.1 從App業務邏輯中提煉API接口 業務邏輯思維導圖 功能-業務邏輯思維導圖 基本功能模塊關系 功能模塊接口UML(設計出API) 在設計稿標注API 編寫API文檔 2.2 設計API的要點 ...

  5. luogu2467/bzoj1925 地精部落 (dp)

    求1~n组成一个抖动序列的方案数 首先这种序列有一些非常妙妙但我发现不了的性质 1.对于一个抖动序列,如果i和i+1不相邻,则交换i和i+1,他还是个抖动序列 2.对于一个抖动序列,我把每个数拿n+1 ...

  6. luogu3953 [NOIp2017]逛公园 (tarjan+dijkstra+记忆化搜索)

    先跑一边dijkstra算出从1到i的最短距离dis[i] 然后建反向边 从n开始记忆化搜索,(p,k)表示1到p的距离=dis[p]+k的方案数 答案就是$\sum\limits_{i=0}^{k} ...

  7. django restframework 环境配置

    Requirements: coreapi (1.32.0+) - Schema generation support.Markdown (2.1.0+) - Markdown support for ...

  8. ie烦人的bug篇

    好多公司都不支持ie6了,基本都是ie8+,就连jq新版本也宣布放弃低版本ie,就不用说那些框架了,不过想用兼容ie的angularjs可以用我司徒大神写的avalon,个人感觉avalon也是比较好 ...

  9. mysql 远程连接 10038

    1,先确认本地是否能连上本地能连上就对用户进行授权 mysql>grant all privileges on *.* to 'root'@'%' identified by 'youpassw ...

  10. JavaScript(JS)之Javascript对象DOM(五)

    https://www.cnblogs.com/haiyan123/p/7653032.html 一.JS中for循环遍历测试 for循环遍历有两种 第一种:是有条件的那种,例如    for(var ...