读书笔记:《流畅的Python》第12章 继承的优缺点

读书笔记:《流畅的Python》第12章 继承的优缺点,第1张

读书笔记:《流畅的Python》第12章 继承的优缺点
# 第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,也不知道为了啥?

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5625141.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-15
下一篇 2022-12-15

发表评论

登录后才能评论

评论列表(0条)

保存