# 第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,也不知道为了啥?
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)