# 第12章 继承的优缺点
"""
内容大纲:
继承和子类化
子类化内置类型的缺点
多重继承和方法解析顺序
"""
# 子类化内置类型很麻烦
# python 2.2 之前不支持内置类型子类化,之后支持了
# 重要注意事项:使用C编写的内置类型不会调用用户定义的类覆盖的特殊方法
# 例如dict的子类覆盖了__getitem__()方法,不会被内置类型的get()方法调用
# 示例 12-1 内置类型的dict的__init__和__update__方法会忽略覆盖的__setitem__
"""
class DoppelDict(dict):
def __setitem__(self, key, value):
super(DoppelDict, self).__setitem__(key,[value] * 2)
dd = DoppelDict(one=1)
print(repr(dd)) # {'one': 1}
dd['two'] = 2
print(repr(dd)) # {'one': 1, 'two': [2, 2]}
dd.update(three = 3)
print(repr(dd)) # {'one': 1, 'two': [2, 2], 'three': 3}
"""
# 原生类型的这种行为违背了面向对象编程的一个基本原则:始终应该从实例所属的类开始搜索方法
# 即使在超类实现的类中也是如此
# 示例12-2 内置类型的方法调用的其他类的方法,如果被覆盖了,也不会调用
# dict.update方法会忽略AnswerDict.__getitem__方法
"""
class AnswerDict(dict):
def __getitem__(self, key):
return 42 #不管传入什么键,都返回42
ad = AnswerDict(a='foo')
print(ad['a']) # 42 符合预期
d = {}
d.update(ad)
print(d['a']) # foo
print(d) # {'a': 'foo'}
"""
# 内置类型dict的实例d的update()方法忽略了AnswerDict.__getitem__方法
# 结论:不要子类化内置类型,用户定义的类应该继承collections中的类
# 如:UserDict UserList UserString等,这些类做了特殊设计,易于扩展
# 示例12-3 DoppelDict2和AnswerDict2能像预期的那样使用
"""
import collections
class DoppelDict(collections.UserDict):
def __setitem__(self, key, value):
super(DoppelDict, self).__setitem__(key,[value] * 2)
dd = DoppelDict(one=1)
print(repr(dd)) # {'one': [1, 1]}
dd['two'] = 2
print(repr(dd)) # {'one': [1, 1], 'two': [2, 2]}
dd.update(three = 3)
print(repr(dd)) # {'one': [1, 1], 'two': [2, 2], 'three': [3, 3]}
class AnswerDict(collections.UserDict):
def __getitem__(self, key):
return 42
ad = AnswerDict(a='foo')
print(ad['a']) # 42
d = {}
d.update(ad)
print(d['a']) # 42
print(d) # {'a': 42}
"""
# 12.2 多重继承和方法解析顺序
# 多重继承存在命名冲突的问题,这是由不相关的祖先类实现同名方法引起的,这种冲突称为菱形问题
# 示例12-4 diamond.py
# 几个类的__mro__属性
print(bool.__mro__)
# >>>(, , )
def print_mro(cls):
print(','.join(c.__name__ for c in cls.__mro__))
print_mro(bool) # bool,int,object
print_mro(str)
import numbers
print_mro(numbers.Integral)
# >>>Integral,Rational,Real,Complex,Number,object
import io
print_mro(io.BytesIO)
# >>>BytesIO,_BufferedIObase,_IObase,object
print_mro(io.TextIOWrapper)
# >>>TextIOWrapper,_TextIObase,_IObase,object
import tkinter
print_mro(tkinter.Text)
# >>>Text,Widget,baseWidget,Misc,Pack,Place,Grid,XView,YView,object
# 12.3 多重继承的真实应用
print_mro(tkinter.Toplevel)
# >>>Toplevel,baseWidget,Misc,Wm,object
print_mro(tkinter.Widget)
# >>>Widget,baseWidget,Misc,Pack,Place,Grid,object
print_mro(tkinter.Button)
# >>>Button,Widget,baseWidget,Misc,Pack,Place,Grid,object
print_mro(tkinter.Entry)
# >>>Entry,Widget,baseWidget,Misc,Pack,Place,Grid,XView,object
# 12.4 处理多重继承
"""
建议
1.把接口继承和实现继承区分开
明确一开始为什么创建子类:
继承接口,创建子类型,实现'是什么'的关系
这是框架的支柱
继承实现,通过重用避免代码重复
可以换用组合和委托的模式
2.使用抽象基类显式表示接口
3.通过混入重用代码
如果一个类的作用是为多个不相关的子类提供实现方法,从而实现重用
但不体现'是什么'的关系,应该把这个类明确定义为混入类(mixin class)
它的作用只是打包方法,绝对不能实例化.
具体的类不能只继承混入类
在名称中明确指明混入如tkinter中的XView变成XViewMixin
4.抽象基类可以作为混入,反过来则不成立
5.不要子类化多个具体的类
具体类可以没有,或者最多有一个具体超类
也就是说具体类的超类除了一个具体超类以外,其他都是抽象基类或混入
6.为用户提供聚合类
如果抽象基类或者混入的组合对客户代码非常有用,那就提供一个类,使用
易于理解的方式把他们组合起来,这种类称为集合类(aggregate class)
如: tkinter.Widget
class Wiget(baseWiget,Pack,Place,Grid):
'''Internal class base class for a widget which can be
position with the geometry managers Pack,Place,Grid'''
pass
定义体是空的
7.优先使用对象组合,而不是类继承
"""
# 12.5 一个现代示例:Django通用视图中的混入
"""
Django中的视图,是可调用的对象,参数是表示HTTP请求的对象,返回值是一个表示HTTP响应的对象
"""
"""
本章总结:
1.不要子类化内置类型,因为行为不一致
2.使用collections中的User...来扩展内置类型
3.熟悉super()方法搜索类方法的顺序,calssName.__mro__,了解c3算法
4.对tkinter和Django在多重继承中的优缺点分析,太复杂了,参看原书.
"""
diamond.py
# diamond.py
# 多重继承的命名冲突产生的菱形问题
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(D, self).ping()
print('post-ping:',self)
def pingpong(self):
self.ping()
super(D, self).ping()
self.pong()
super(D, self).pong()
C.pong(self)
# 如想绕过方法解析顺序,直接调用某个超类的方法,可以这样写:
# def ping(self):
# A.ping(self) # 必须显式传入参数
# print('post-ping:',self)
if __name__ == '__main__':
d = D()
# d.pong()
# d.pingpong()
# 类查找方法的顺序叫做mro(Method Resolution Oder) 保存在类的__mro__属性中
# print(D.__mro__)
# (, ,
# , , )
# 若想把方法调用委托给超类,推荐的方式是super()函数
d.ping()
35岁学Python,也不知道为了啥?



