[TOC]

第二部分 数据结构

Chapter2 List

  1. 列表推导式/生成器表达式在 Python3 有自己的局部作用域, 类似函数. 而 Python2 没有

  2. 元组, 还可以用于没有字段名的记录

  3. collections.namedtuple 工厂函数

    作用: 构建一个带字段名的元组, 一个有名字的类

     ::Python
     from collections import namedtuple
     In [32]: City = namedtuple('City', 'name country population coo
         ...: rdinates')
     In [33]: tokyo = City('Tyokyo', 'JP', 23,(35.6, 139.6))
     In [34]: tokyo
     Out[34]: City(name='Tyokyo', country='JP', population=23, coordinates=(35.6, 139.6))
     In [54]: City._fields
     Out[54]: ('name', 'country', 'population', 'coordinates')
     In [47]: tokyo._asdict()
     Out[47]:
     OrderedDict([('name', 'Tyokyo'),
                  ('country', 'JP'),
                  ('population', 23),
                  ('coordinates', (35.6, 139.6))])
     # 第一个参数'City'是类名, 第二个参数是类的各个字段的名字, 或者一个包含字符串的可迭代对象
     # 属性: 类属性:  _make() _field 实例属性: _asdict() _field
    

    namedtuple源码解析

  4. 使用名字切片

     ::Python
     sli = slice(0, 6) # 创建切片
     string = '0123456'
     string[sli]       # 相当于 string[0,6]
    
  5. 列表操作符 * my_list = [[]] * 3 中的三个列表都是引用了同一个列表

  6. 如果实现了特殊方法 __isadd__, += 的作用是在原对象上进行改动, 反之的话, 会创建新的对象. 相同的, *= 对应 __imul__

  7. tuple 的一个坑

     ::Python
     In [59]: t = (1, 2, [30, 40])
     In [60]: t[2] += [50, 60]
     # 结果: 报错 'TypeError: 'tuple' object does not support item assignment'
     # 并且值改变
     In [62]: t[2]
     Out[62]: [30, 40, 50, 60]
    
    • s[a] += b 分为三步
      1. s[a]的值存入 TOS(Top Of Stack), 栈的顶端
      2. 计算 TOS += b
      3. s[a] = TOS TOS赋值, 因为 s 是不可变, 报错
  8. list.sortsorted 内部算法是 Timsort, 交替使用插入排序归并排序

    • 相同参数: reverse key
      • reverse 默认 False 为升序
      • key 例子: key=str.lower, key=len(按长度升序), key=str, key=int
  9. bisect 模块, 两个函数 bisectinsort, 都利用了二分查找算法

    • bisect(haystack, needle) 在haystack里搜索needle的位置
    • insort(seq, item) 把变量 item 插入到seq中
  10. array.array array(typecode [, initializer]) -> array d 类型是一个双精度浮点数组, array.tofile array.fromfile

  11. memoryview

    • memory(number).cast('B') 将memv里的内容转换为’B’类型, 即无符号字符

Chapter3 Dict and Set

  1. 常见字

    1. 字典推导式:

       ::Python
       DIAL_CODES = [(86, 'China'), (91, 'India'), (1, 'United States')]
       country_code = {country: code for code, country in DIAL_CODES}    
      
    2. 常见映射方法 dict collections.defaultdict collections.OrderedDict

  2. 如何处理查找不到的键

    • 使用 d.get(k, default)

        :::python
        my_dict.setdefault(key, []).append(new_valule)
        # 相当于
        if key no in my_dict:
            my_dict[key] = []
        mydict[key].append(new_value)
      
    • __missing__ 当在 __getitem__ 碰到找不到的键的时候, 会调用__missing__方法 而对get或者__contains__(in运算符)没有影响

  3. 标准库中dict类型的变种

    1. collections.OrderedDict 添加时保持顺序
    2. collections.ChainMap 可以容纳数个不同的映射对象
    3. collections.counter 可以接受一个字符串, 统计各个字母出现的次数
  4. set 和 frozenset 类型

    • 集合中的元素必须是可散列的, set 不可散列, frozenset 可散列

    • 空集必须写成 set() 形式

        ::Python
        # 求 needles 元素在 haystack 里出现的次数
        found = len(needles & haystack) 
      
  5. 散列表工作原理

    1. 散列表是一个稀疏数组(总有空白元素的数组称为稀疏(sparse)数组), 其中元素叫做表元(bucket) dict 散列表中, 每个键值对占用一个表元, 一个表元分两个部分, 对键的引用和对值的引用, 所有表元的大小是一致的. Python3.3后, str, bytes, datetime 对象的散列值过程中多了随机’加盐’,目的是为了防止 Dos 攻击而采取的一种安全措施. Python 保证表元三分之一是空的, 当即将满的时候, 原有散列表会被复制到一个更大的空间里.
    2. 可散列: 如果一个对象是可散列的, 那么在这个对象生命周期中, 他的散列值是不变的. 而且需要实现两个方法 __hash__(), __qe__() (可以与其他键做比较)
    3. 可散列类型: str bytes 数值类型 frozenset, 不一定是不可变类型(比如含有可变类型对象的元组)
    4. 散列表算法:
      • 以键查找值: 调用 hash(search_key)来计算 search_key 的散列值, 取值的末尾几位数字当作偏移量, 通过这个偏移量在散列表里面查找表元, 如果为空报异常, 不为空的话, 检查search_key == found_key 是否为真, 真就返回found_value, 如果为假, 称为散列冲突, 会多取几位 search_key 的末尾数字重新计算偏移量, 重新计算表元, 若依然不相等, 则重复此步骤.
      • 更新键值: 同查找一样, 只不过找到表元后, 更新值对象
  6. 散列表原理带来的字典特点(键类型, 顺序)

    1. 键必须是可散列的
      • 支持hash() 函数, 并通过__hash__()方法得到的散列值不变
      • 支持通过__eq__()方法检测相等性
      • 若 a==b, 则 hash(a)==hash(a)
      • 所有用户自定义的对象默认为可散列
    2. 字典占内存大
      • 原因: 使用了稀疏(sparse)的散列表
      • 可以使用元组代替字典存储大的数据, 原因:1)避免了散列表产生的空间.2)无需把记录中字段的名字在每个元素里都存一遍
    3. 查询速度快
      • 使用空间换时间
    4. 键的次序取决于添加顺序
      • 相等的字典可能键的次序不一样
    5. 添加新键可能改变已有键的顺序
      • 因为可能由于扩容造成散列冲突, 导致次序变化. 所以在遍历的字典的时候同时对字典进行修改会出错
  7. set 的实现及导致的结果

    1. set/forzenset 的实现也依赖于散列表, 但散列表存放的只是元素的引用(A set is a dictionary without storage for the value)
    2. 字典/散列表/集合共同的特点:
      • 集合里的元素必须是可散列的
      • 集合很消耗内存
      • 集合可以高效的判断元素是否位于某个集合
      • 元素的次序取决于被添加到集合里的次序
      • 集合添加新元素, 可能会改变已有元素的次序
  8. 第三章扩展

    1. 代码之美18章-Python字典类:如何打造全能战士 CPython dictobject.c源文件
    2. PEP274
    3. Brandon Rhodes 的讲座"The Mighty Dictionary”

Chapter4 Text versus Bytes

  1. 字符, 码位, 字节

    • 码位, 字符的标识, Unicode标准中以4~6个十六进制数字表示, 且加前缀"U+”
    • Python2 的bytes类型不同于 Python3 的bytes类型
  2. bytes, bytearray, memoryview

    • bytes()方法参数:
      • 一个str对象, 一个encoding关键字参数
      • 一个可迭代对象, 提供0~255之间的数值
      • 一个整数(Python3.6已经移除)
      • 一个实现了缓冲协议的对象(如bytes/bytearray/memoryview/array.array)
  3. Unicode, 陈旧字符集的编解码器

  4. 编码错误

    • encode('cp237', errors='ignore')或者encode('cp437', errors='replace'), ‘replace’是把无法编码的字符替换为’?’
  5. 处理文本文件的最佳实践

  6. 默认编码, 标准I/O问题

  7. 规范Unicode文本

  8. 规范化的实用函数

  9. 使用locale模块和PyUCA库正确的排序Unicode文本

  10. Unicode 数据库中的字符元数据

  11. 字符串和字节序列都能处理的API


第三部分 把函数视作对象

Chapter5 一等函数(first-class)

  1. 一等对象: 1)在运行时创建 2)能赋值给变量 3)能作为参数传递给函数 4) 能作为函数的返回结果

  2. 函数式编程

  3. 高阶函数(接受函数作为参数, 或者把函数作为结果返回)

  • map, sorted, filter, reduce …
  1. lambda:
  • sorted(my_list, key=lambda word: word[::-1])
  1. 7种可调用对象: 用户定义的函数, 内置函数, 内置方法, 方法, 类, 类的实例, 生成器函数

  2. 函数内省 __dict__

  3. 函数参数. 函数对象有个 __default__ 属性, 值是一个元组, 保存着定位参数和关键字参数的默认值

  4. inspect.Signature

  5. 函数注解 ->

    • -> str: 注解的意思是返回的对象类型是字符串

    • 注解部分会存储在__annotations__ 属性(一个字典)中

        ::Python
        def clip(text:str, max_len:'int > 0'=80) -> str:
        ...
        >>> clip.__annotations__
        {'text': <class 'str'>, 'max_len': 'int > 0', 'return': <class 'str'>}
      
  6. 支持函数式编程的包 - operator

    1. reduce 计算阶乘

       ::Python
       from functools import reduce
       def fact(n):
           return reduce(lambda x, y: x*y, range(1, n+1))
      
    2. operator.mul

       ::Python
       from functools import reduce
       from operator import mul
       def fact(n):
           return reduce(mul, range(1, n+1))
      
    3. operator.attrgetter sorted(metro_areas, key=attrgetter('coord.lat'))

    4. operator.methodcaller

       ::Python
       In [124]: from operator import methodcaller
       In [125]: s = 'The time has come'
       In [129]: hiphenate = methodcaller('replace', ' ', '-')
       In [130]: hiphenate(s)
       Out[130]: 'The-time-has-come'
      
    5. functools.partial partial 第一参数是一个可调用对象, 后面是任意个要绑定的定位参数和关键字参数

       ::Python
       >>> from operator import mul
       >>> from functools import partial
       >>> triple = partial(mul, 3)
       >>> triple(7)
       27
      

Chapter6 First Class 设计模式(重要)

  1. 三个折扣规则

    • 1000积分以上的顾客, 每个订单%5折扣
    • 同一订单中, 单个商品数量20个以上, 享10%折扣
    • 订单中不同商品达到10个以上, 享7%折扣
  2. 几种设计模式:

    • 第一种: 构造一个 Promotion 抽象基类, 三个折扣规则分为三个类继承自基类
    • 第二种: 无抽象基类, 规则为函数实现
    • 第三种: 函数实现规则, 使用列表存储, 然后使用生成器遍历
    • 第四种: 使用 globals() endswith('_promo’)
    • inspect.getmembers()
    • 使用装饰器讲函数注册到列表, 然后再使用取列表最大折扣
      • 好处: 简洁, 启用禁用方便, 装饰器可以在其他模块中定义, 解耦

Chapter7 函数装饰器和闭包

  1. 基础

    1. 特点
      • 将函数替换成其他函数
      • 装饰器在加载模块时立即执行
    2. 执行装饰器的时间
      • 装饰器导入模块立即执行
      • 被装饰的函数在明确调用时运行
  2. 局部变量的判断

    • Python 不要求声明变量, 但是会假定变量为局部变量
    • 使用 dis.dis 模块, 反汇编 Python 函数字节码
    • LOAD_FAST 加载本地
    • LOAD_GLOBAL 加载全局
  3. 闭包作用/工作原理

    • avg 函数计算不断增加的总值的平均值

    • 其中 series 是一个自由变量(free variable)

        ::Python
        In [9]: avg.__code__.co_varnames   # 局部变量
        Out[9]: ('new_value', 'total')
        In [10]: avg.__code__.co_freevars  # 自由变量
        Out[10]: ('series',)
      
    • __code__ 表示编译后的函数定义体

        ::Python
        In [11]: avg.__closure__
        Out[11]: (<cell at 0x10436cb58: list object at 0x104652588>,)
        In [12]: avg.__closure__[0].cell_contents
        Out[12]: [12, 19, 24]
      
    • series的绑定储存在 __closure__ 属性中, 每一个元素是一个 cell对象

  4. nonlocal

  • 作用: 把变量标记为自由变量, 即在函数或其他作用域中使用外层(非全局)变量。
  1. 实现装饰器

    1. 普通装饰器的缺点:

      • 覆盖了被装饰器的 __name__ __doc__ 属性
      • 不支持关键字参数
    2. 使用 functools.wraps 改进以上的缺点

       ::Python
       import time
       import functools
      
       def clock(func):
           @functools.wraps(func)
           def clocked(*args, **kwargs):
               t0 = time.time()
               result = func(*args, **kwargs)
               elapsed = time.time() - t0
               name = func.__name__
               arg_lst = []
               if args:
                   arg_lst.append(', '.join(repr(arg) for arg in args))
               if kwargs:
                   print('[{:.8f}s] {}({}) -> {!r}'.format(elapsed, name, arg_str, result))
                   arg_lst.append(', '.join(pairs))
               arg_str = ', '.join(arg_lst)
               print('[%0.8fs] %s(%s) -> %r ' % (elapsed, name, arg_str, result))
               return result
           return clocked
      
  2. 标准库中的装饰器, 内置三个: property, classmethod, staticmethod

    • functools.wraps

    • functools.lru_cache

      • 作用: lru(Least Recently Used), 缓存一定长度的函数结果, 可以重复利用
    • functools.singledispatch

      • 作用: @singledispatch 装饰器将被装饰的普通函数变成范函数(根据第一个参数的类型), 以不同方式执行相同操作的一组函数.
    • 优点: 支持模块化扩展, 各个模块可以为它支持的各个类型注册一个专门函数, 可以代替if/elif/elif...

            ::Python
            from functools import singledispatch
            from collections import abc 
            import numbers
            import html
            @singledispatch
            def htmlize(obj):
                content = html.escape(repr(obj))
                return '<pre>{}</pre>'.format(content)
            @htmlize.register(str)
            def _(text):
                content = html.escape(text).replace('\n', '<br>\n')
                return '<p>{}</p>'.format(content)
            @htmlize.register(numbers.Integral)
            def _(n):
                return '<pre>{0} (0x{0:x})</pre>'.format(n)
            @htmlize.register(tuple)
            @htmlize.register(abc.MutableSequence)
            def _(seq):
                inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
                return '<ul>\n<li>' + inner + '</li>\n</ul>'
        print(htmlize(78))
      
  3. 带参数装饰器

     ::Python
     import time
     DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'
     def clock(fmt=DEFAULT_FMT):
         def decorate(func):
             def clocked(*args, **kwargs):
                 t0 = time.perf_counter()
                 _result = func(*args, **kwargs)
                 elapsed = time.perf_counter() - t0
                 name = func.__name__
                 arg_str = ', '.join(repr(arg) for arg in args)
                 result = repr(_result)
                 print(fmt.format(**locals()))
                 return _result
             return clocked
         return decorate
        
     @clock
     def snooze(seconds):
         time.sleep(seconds)
     for i in range(3):
         snooze(.123)
    

第四部分 面向对象惯用法

Chapter8 对象引用/可变性/垃圾回收

0. 对象标识, 值, 元组的不可变性, 但其中值可变…

1. Python 变量不是’盒子’, 是’标签’/‘标签’

2. 标识, 相等性, 别名

  1. ==is
    • == 比较的两个对象中保存的数据, is 比较对象的标识
    • 推荐写法: x is None, x is not None
    • is== 运算速度快, 因为它不能重载
    • a == b 是语法糖, 等同于 a.__eq__(b), 继承自object的__eq__方法比较两个对象的ID, 结果与 is一样.
  2. 元组的不可变性是指保存的引用不变, 与引用的对象无关, 即如果元组内保存可变对象(如列表), 则列表内元素可变

3. 浅复制

  • 列表中, 构造方法list()[:] 是浅复制(只复制容器, 副本中的元素是对原容器中的元素做引用, 如果改变副本中的可变元素, 原容器中的元素也会相应改变
  • 特殊方法: __copy__ __deepcopy__

4. 函数的参数作为引用时

  • 参数传递类型: 共享传参(call by sharing), 即 形参为实参的引用, 形参是实参的别名, 使用此模式的其他面向对象语言:Ruby, Smalltalk, Java的引用类型

  • Python函数的参数默认值,是在编译阶段就绑定的。默认参数是函数的属性,它的值可能会随着函数被调用而改变。

  • 不要使用可变类型作为参数的默认值

  • 预防可变参数导致的坑: 1. 默认参数设置为 None 2. 当为 None 时初始化参数

      ::Python
      def __init__(self, passengers=None):
          if passengers is None:
              self.passengers = []
          else:
              self.passengers = list(passengers)
    

5. del 和垃圾回收

  1. __del__ 销毁实例时调用, 释放外部资源

  2. del s1 不删除对象, 而是删除对象的引用. del 并不一定会调用 __del__ 魔术方法

     ::Python
     In [1]: import weakref
     In [2]: s1 = {1, 2}
     In [3]: s2 = s1
     In [4]: def bye():
        ...:     print('Gone with the wind..')
        ...:
     In [5]: ender = weakref.finalize(s1, bye) 
     # 给 s1引用的对象(即{1,2}) 注册 bye 回调, 当 对象 gc 时调用 bye
     In [6]: ender.alive
     Out[6]: True
     In [8]: del s1
     In [9]: ender.alive
     Out[9]: True
     # del s1 只是删除对象的引用, 没有 gc
     In [10]: s2 = 'spam'
     Gone with the wind..
     In [11]: ender.alive
     Out[11]: False
     # s2 不再引用 对象{1,2} 后, 对象被引用数为0,  调用 gc 对象被销毁, 引发回调
     # weakref.finalize() 是弱引用, 不增加引用计数
    

Help on finalize in module weakref object: class finalize(builtins.object) | Class for finalization of weakrefable objects | | finalize(obj, func, *args, **kwargs) returns a callable finalizer | object which will be called when obj is garbage collected. The | first time the finalizer is called it evaluates func(*arg, **kwargs) | and returns the result. After this the finalizer is dead, and | calling it just returns None.

6. 弱引用

  1. 弱引用不增加引用数量, 即不影响垃圾回收(gc)

  2. 使用 weakref.ref 实例获取所指对象 (注意以下代码要用自带的 REPL, 不要用 Ipython, 具体解释:

    • The Python interpreter assigns the last expression value to _

    • 只有 Python 自带的解释器才有这个特性

        ::Python
        >>> import weakref
        >>> a_set = {0,1}
        >>> wref = weakref.ref(a_set)
        >>> wref
        <weakref at 0x1068dc138; to 'set' at 0x1068d44a8>
        >>> wref()
        {0, 1}
        # 调用wref() 返回, 对象 {0, 1} 会被 `_` 引用
        >>> a_set = {123,123,123}
        >>> # a_set 引用其他对象, {0,1}对象 仅被 `_` 引用
        >>> wref()
        {0, 1}
        >>> wref() is None
        False
        # 调用这个表达式时, {0,1}被 `_` 引用, 但是之后 `_` 绑定到 False, {0,1} 对象被引用数为0
        >>> wref() is None
        True
      
  3. WeakValueDictionary

    • WeakValueDictionary 类实现的是可变映射, 值是对象的弱引用, 当此对象被 GC 后, 对应的键会自动删除, 相应的还有 WeakKeyDictionary

    • 下列代码中 del catalog 之后 Parmesan 还存在的原因是, 在之前的 for 循环中, 临时变量 cheese 引用了对象, 可能会导致变量的存在时间比预期长, 显式删除才能完全删除

        ::Python
        class Cheese():
            def __init__(self, kind):
                self.kind = kind
            def __repr__(self):
                return 'Cheese(%r)' % self.kind
      
        import weakref
        stock = weakref.WeakValueDictionary()
        catalog = [Cheese('Red Leicester'), Cheese('Tilsit'),
                        Cheese('Brie'), Cheese('Parmesan')]
        for cheese in catalog:
            stock[cheese.kind] = cheese
      
        sorted(stock.keys())
        # ['Brie', 'Parmesan', 'Red Leicester', 'Tilsit']
        del catalog
        sorted(stock.keys())
        ['Parmesan']
        del cheese
        sorted(stock.keys())
        # []
      
  4. 弱引用的局限(CPython)

    • set 可以作为弱引用的目标
    • 基本的 list 和 dict 不能作为弱引用的目标, 但是其子类可以
    • int 和 tuple 及其子类都不能作为弱引用的目标

7. Python 不可变类型的一些小特性

  1. t 是一个元组, 那么 t[:] 和 tuple(t) 不能创建副本, 而是返回同一个对象的引用, str, bytes, forzeset 也都这样
  2. 常用字符串及数字在内存中驻留, 是 Cpython 的一些优化细节

Chapter9 符合 Python 风格的对象

0. 本章要点:

  • 生成对象其他表示形式的内置函数 (如 repr(), bytes())
  • 使用类方法实现备选构造函数
  • 扩展 format(), str.format()
  • 实现只读属性
  • 把对象变为可散列的, 以便在集合中作为 dict的键使用
  • 使用 __slots__ 的主要需求是节省内存, 而不是为了避免创建实例属性 概念:
  • @classmethod 与 @staticmethod 的用法
  • 私有属性(@private), 受保护属性的用法, 约定, 局限

1. 对象表示形式

  1. repr() 以 便于开发者理解的方式返回对象的字符串表示形式, __repr__
  2. str() 以 便于用户理解的方式返回对象的字符串表示形式, __str__
  3. __bytes__

2. 向量类

::Python
from array import array 
import math

class Vector2d:
    typecode = 'd'
    def __init__(self, x, y):
        self.x = float(x)
        self.y = float(y)
    # In [5]: v1 = Vector2d(3, 4)
    # In [6]: print(v1.x, v1.y)
    # 3.0 4.0

    def __iter__(self):
        return (i for i in (self.x, self.y))
    # In [8]: x, y = v1
    # In [9]: x,y
    # Out[9]: (3.0, 4.0)

    def __repr__(self):
        class_name = type(self).__name__
        return '{}({!r},{!r})'.format(class_name, *self)
    # In [7]: v1
    # Out[7]: Vector2d(3.0,4.0)

    def __eq__(self, other):
        return tuple(self) == tuple(other)
    # In [10]: v1_clone = eval(repr(v1))
    # In [11]: v1 == v1_clone
    # Out[11]: True

    def __str__(self):
        # return (self.x, self.y)
        return str(tuple(self))
    # In [12]: print(v1)
    # (3.0, 4.0)

    def __bytes__(self):
        return (bytes([ord(self.typecode)]) +
                bytes(array(self.typecode, self)))
    # In [13]: oetets = bytes(v1)
    # Out[13]: b'd\\x00\\x00\\x00\\x00....@'
    
    def __abs__(self):
        return math.hypot(self.x, self.y) 
    # In [15]: abs(v1)
    # Out[15]: 5.0

    def __bool__(self):
        return bool(abs(self))
    # In [16]: bool(v1), bool(Vector2d(0, 0))
    # Out[16]: (True, False)
  • Normally str.format() will call the object.format() method on the object itself, but by using !r, repr(object).format() is used instead.

3. 备选构造方法

  • 在类 Vector2d 中添加方法 frombytes

      ::Python
      @classmethod 
      def frombytes(cls, octets):
          typecode = chr(octets[0])
          # memoryview() 函数返回给定参数的内存查看对象(Momory view)。
          # 所谓内存查看对象,是指对支持缓冲区协议的数据进行包装,在不需要复制对象基础上允许Python代码访问。
          # 对象符合缓冲区协议的对象,为了给别的代码使用缓冲区里的数据,而不必拷贝,就可以直接使用。
          memv = memoryview(octets[1:]).cast(typecode)
          return cls(*memv)
    

4. classmethod staticmethod

5. 格式化显示

  1. format() 函数, 两种方式:

     ::Python
     In [3]: brl = 1/2.43
     In [4]: brl
     Out[4]: 0.4115226337448559
     In [6]: format(brl, '0.4f')
     Out[6]: '0.4115'
     In [7]: '1 BRL = {rate:0.2f} USD'.format(rate=brl)
     Out[7]: '1 BRL = 0.41 USD'
    
  2. 格式规范微语言文档

     ::Python
     def __format__(self, fmt_spec=''):
         # 以 'p' 结尾, 使用极坐标
         if fmt_spec.endswith('p'):
             fmt_spec = fmt_spec[:-1]
             coords = (abs(self), self.angle())
             outer_fmt = '<{}, {}>'
         else:
             coords = self
             outer_fmt = '({}, {})'
         components = (format(c, fmt_spec) for c in coords)
         return outer_fmt.format(*components)
    
     def angle(self):
         return math.atan2(self.y, self.x)
    

6. 可散列的 Vector2d

  1. 可散列需要实现两个方法: __hash__ __eq__, 所以需要使变量(self.x)不可变

  2. 全部代码:

     ::Python
     from array import array 
     import math
    
     class Vector2d:
         '''
         >>> v1 = Vector2d(3, 4)
         >>> print(v1.x, v1.y)
         3.0 4.0
         >>> x, y = v1
         >>> x, y
         (3.0, 4.0)
         >>> v1
         Vector2d(3.0,4.0)
         >>> v1_clone = eval(repr(v1))
         >>> v1 == v1_clone
         True
         >>> print(v1)
         (3.0, 4.0)
         >>> abs(v1)
         5.0
         >>> bool(v1), bool(Vector2d(0, 0)) 
         (True, False)
    
     Test of ``.formbytes()`` class method:
    
         >>> v1_clone = Vector2d.frombytes(bytes(v1))
         >>> v1_clone
         Vector2d(3.0,4.0)
         >>> v1 == v1_clone
         True
    
     Tests of ``format()`` with Cartesian coordinates:
    
         >>> format(v1)
         '(3.0, 4.0)'
         >>> format(v1, '.2f')
         '(3.00, 4.00)'
         >>> format(v1, '.3e')
         '(3.000e+00, 4.000e+00)'
    
     Tests of the ``angle`` method:
    
         >>> Vector2d(0, 0).angle()
         0.0
         >>> Vector2d(1, 0).angle()
         0.0
         >>> epsilon = 10**-8
         >>> abs(Vector2d(0,1).angle() - math.pi/2) < epsilon
         True
         >>> abs(Vector2d(1,1).angle() - math.pi/4) < epsilon 
         True
    
     Test of ``format()`` with polar coordinates:
    
         >>> format(Vector2d(1, 1), 'p') 
         '<1.4142135623730951, 0.7853981633974483>'
         >>> format(Vector2d(1, 1), '.3ep')
         '<1.414e+00, 7.854e-01>'
         >>> format(Vector2d(1, 1), '0.5fp')
         '<1.41421, 0.78540>'
    
     Test of hashing:
    
         >>> v1 = Vector2d(3,4)
         >>> v2 = Vector2d(3.1, 4.2)
         >>> hash(v1)
         7
         >>> len(set([v1, v2]))
         2
         '''
         typecode = 'd'
         __slots__= ('__x', '__y')
            
         def __init__(self, x, y):
             self.__x = float(x)
             self.__y = float(y)
         @property
         def x(self):
             return self.__x
         @property
         def y(self):
             return self.__y    
    
         def __iter__(self):
             return (i for i in (self.x, self.y))
         # In [8]: x, y = v1
         # In [9]: x,y
         # Out[9]: (3.0, 4.0)
    
         def __repr__(self):
             class_name = type(self).__name__
             return '{}({!r},{!r})'.format(class_name, *self)
         # In [7]: v1
         # Out[7]: Vector2d(3.0,4.0)
    
         def __eq__(self, other):
             return tuple(self) == tuple(other)
         # In [10]: v1_clone = eval(repr(v1))
         # In [11]: v1 == v1_clone
         # Out[11]: True
    
         def __str__(self):
             # return (self.x, self.y)
             return str(tuple(self))
         # In [12]: print(v1)
         # (3.0, 4.0)
    
         def __bytes__(self):
             return (bytes([ord(self.typecode)]) +
                     bytes(array(self.typecode, self)))
         # In [13]: oetets = bytes(v1)
         # Out[13]: b'd\\x00\\x00\\x00\\x00....@'
            
         def __abs__(self):
             return math.hypot(self.x, self.y) 
         # In [15]: abs(v1)
         # Out[15]: 5.0
    
         def __bool__(self):
             return bool(abs(self))
         # In [16]: bool(v1), bool(Vector2d(0, 0))
         # Out[16]: (True, False)
    
         def __format__(self, fmt_spec=''):
             # 以 'p' 结尾, 使用极坐标
             if fmt_spec.endswith('p'):
                 fmt_spec = fmt_spec[:-1]
                 coords = (abs(self), self.angle())
                 outer_fmt = '<{}, {}>'
             else:
                 coords = self
                 outer_fmt = '({}, {})'
             components = (format(c, fmt_spec) for c in coords)
             return outer_fmt.format(*components)
    
         def angle(self):
             return math.atan2(self.y, self.x)
    
         def __hash__(self):
             return hash(self.x) ^ hash(self.y)
    
         @classmethod
         def frombytes(cls, octets):
             typecode = chr(octets[0])
             memv = memoryview(octets[1:]).cast(typecode)
             return cls(*memv)
    
     if __name__=='__main__':
         import doctest
         doctest.testmod(verbose=True)
    

7. Python 的私有属性 和 “受保护的” 属性

双下划线前缀私有属性, 使用 ._classname__attribute 还是可以访问到的, 并不是真正的私有和不可变

::Python
In [2]: v1 = Vector2d(3, 4)
In [3]: v1.__dict__
Out[3]: {'_Vector2d__x': 3.0, '_Vector2d__y': 4.0}
In [4]: v1._Vector2d__x
Out[4]: 3.0

8. 使用 slots 类属性节省空间

  1. __dict__ 存储实例属性, 是一个字典, 会消耗大量内存

  2. __slots__ 可以限定属性, 节省内存

     ::Python
     class Vector2d:
         __slots__= ('__x', '__y')
    
  3. 特点:

    • 继承 __slots__ 属性没有效果, Python只会使用各个子类中定义的 slots
    • 类中定义了 slots 属性后, 实例中只能使用其中所列的属性
    • __weakref__ 属性默认存在, 但是在定义 __slots__ 的类中, 需要把 __weakref__ 添加到 __slots__ 中, 才能使用弱引用

9. 覆盖类属性

子类属性可以覆盖父类属性

Chapter10 序列的修改, 散列和切片

0. 本章要点

  • 鸭子类型与协议
  • 序列, __len__, __getitem__
  • 切片
  • __getattr__ 实现属性的动态存取

1. Vector 类: 用户定义的序列类型

  • 使用组合模式实现 Vector 类, 而不适用继承.
  • 浮点数组, 不可变扁平序列方法

2. Vector 类

  • code:

      ::Python
      def __init__(self, components):
          self._components = array(self.typecode, components)
      def __iter__(self):
          # return (i for i in (self.x, self.y))
          return iter(self._components)
      def __repr__(self):
          # reprlib.repr() 函数获取对象的邮箱长度表示形式
          components = reprlib.repr(self._components)
          # 提取列表形式的str
          components = components[components.find('['):-1]
          return 'Vector({})'.format(components)
          # class_name = type(self).__name__
          # return '{}({!r},{!r})'.format(class_name, *self)
      def __bytes__(self):
          return (bytes([ord(self.typecode)]) +
                  # bytes(array(self.typecode, self)))
                  bytes(self._components))
      def __abs__(self):
          # return math.hypot(self.x, self.y) 
          return math.sqrt(sum(x*x for x in self))
      @classmethod
      def frombytes(cls, octets):
          typecode = chr(octets[0])
          memv = memoryview(octets[1:]).cast(typecode)
          # return cls(*memv)
          return cls(memv)
    

3. 协议和鸭子类型

  • 序列协议只需要实现 __len__ __getitem__ 方法

4. 切片

  1. 简单实现: Vector 的实例是数组

     ::Python
     # 实现切片:
     def __len__(self):
         return len(self._components)
     def __getitem__(self, index):
         return self._components[index]
    
  2. 切片原理

  • slice 函数, 将平常使用的索引转化为标准形式

      ::Python
      In [14]: slice(None, 10, 2).indices(5)
      Out[14]: (0, 5, 2)
      # 'ABCDE'[:10:2] 等同于 'ABCDE'[0:5:2]
      In [15]: slice(-3, None, None).indices(5)
      Out[15]: (2, 5, 1)
      # 'ABCDE'[-3:] 等同于'ABCDE'[2:5:1]
    
  1. 能处理切片的 getitem 方法

     ::Python
     # 实现切片的两个方法:
     def __len__(self):
         return len(self._components)
     def __getitem__(self, index):
         # return self._components[index]
         cls = type(self)
         # 如果 index 参数的值是 slice 对象
         if isinstance(index, slice):
             # 调用类的构造方法, 使用_components 数组的切片构造一个新的实例
             return cls(self._components[index])
         # 如果 index 是 int     
         elif isinstance(index, numbers.Integral):
             return self._components[index]
         else:
             msg = '{cls.__name__} indices must be integers'
             raise TypeError(msg.format(cls=cls))
    

5. 动态存取属性

  • 简单来说, 对于 my_obj.x 表达式, Python

    1. 会检查 my_obj 实例有没有名为 x 的属性
    2. 如果没有, 到类(my_obj.__class__) 中查找
    3. 根据继承顺序树查找
    4. 调用 my_obj 所属类中定义的 __getattr__(self, attrname) 方法, attrname 是属性名称的字符
  • __getattr__ 方法:

      ::Python
      shortcut_names = 'xyzt'
      def __getattr__(self, name):
          cls = type(self)
          if len(name) == 1:
              pos = cls.shortcut_names.find(name)
              if 0 <= pos < len(self._components):
                  return self._components[pos]
          msg = '{.__name__!r} object has no attribute {!r}'
          raise AttributeError(msg.format(cls, name))
    
  • __getattr__ 方法的内部机制:

      ::Python
      In [39]: v = Vector(range(5))
      In [40]: v
      Out[40]: Vector([0.0, 1.0, 2.0, 3.0, 4.0])
      In [41]: v.x
      Out[41]: 0.0
      In [42]: v.x = 10
      In [43]: v.x
      Out[43]: 10
      In [44]: v  # 向量的分量没有变
      Out[44]: Vector([0.0, 1.0, 2.0, 3.0, 4.0])
    
    • 仅当对象没有属性时, 才会调用 __getattr__
    • v.x = 10, 赋值之后 v 对象有 x 属性了, 所以在此访问 x 属性会直接调用值
    • __getattr__ 方法的实现是从 self._components 属性中获取 shortcut_names 所列的"虚拟属性”
  • __setattr__ 方法:

6. 散列和快速等值测试(TODO…)

  • 可散列需要实现的两个方法: __hash__, __eq__

  • 计算 0 到 5 累计异或的三种方式:

      ::Python
      In [47]: import functools
      In [48]: n = 0
      In [49]: for i in range(1,6):
          ...:     n ^= i
          ...:
      In [50]: n
      Out[50]: 1
      In [51]: functools.reduce(lambda a, b: a^b, range(6))
      Out[51]: 1
      In [52]: import operator
      In [53]: functools.reduce(operator.xor, range(6))
      Out[53]: 1
    
  • 代码:

      ::Python
      def __eq__(self, other):
          return tuple(self) == tuple(other)
    
      def __hash__(self):
          # generator, 生成器表达式
          # hashes = (hash(x) for x in self._components)
          # return functools.reduce(operator.xor, hasher, 0)
          hashes = map(hash, self._components)
          return functools.reduce(operator.xor, hashes)
    

7. 格式化(TODO)

Chapter11 接口: 从协议到抽象基类

0. 本章要点:

  • 要点从鸭子类型的代表特征动态协议, 到 抽象基类(Abstract Base Class)
  • 抽象基类的常见用途: 实现接口时作为超类使用
    1. 抽象基类如何检查具体子类是否符合接口定义
    2. 如何使用注册机制声明一个类实现了某个接口, 而不进行子类化操作
    3. 如何让抽象基类自动"识别"任何符合接口的类-不进行子类化或注册

1. Python 中的接口和协议

  • 接口: 对象公开方法的子集, 让对象在系统中扮演特定的角色
  • 接口是实现特定角色的方法集合
  • 协议是接口, 但不是正式的(只由文档和约定定义), 因此协议不能像正式接口那样施加限制.
  • 对于 Python 程序员来说, “x 对象” “x 协议” “x 接口"是一个意思

2. Python 喜欢序列

  • 仅实现 __getitem__ 可以使用 in 操作, 迭代, 访问元素

  • 实现序列协议的一部分, 没有实现__iter____contains__ 也能运行

      ::Python
      In [1]: class Foo:
         ...:     def __getitem__(self, pos):
         ...:         return range(0, 30, 10)[pos]
         ...:
      In [2]: f = Foo()
      In [3]: f[1]
      Out[3]: 10
      In [4]: f[1]
      Out[4]: 10
      In [5]: for i in f: print(i)
      0
      10
      20
      In [6]: 20 in f
      Out[6]: True
      In [7]: 50 in f
      Out[7]: False
    
  • 鸭子类型: 如果走起路来像鸭子,叫起来也像鸭子,那么它就是鸭子。一个对象的特征不是由它的类型决定,而是通过对象中的方法决定.

3. 使用猴子补丁在运行时实现协议

  • 将一个函数复制给类的 __setitem__ 属性

      ::Python
      In [43]: def set_card(deck, position, card):
          ...:     deck._cards[position] = card
          ...:
      In [44]: FrenchDeck.__setitem__ = set_card
    
  • 猴子补丁: 在运行时修改类或模块, 而不改动源码

4. Alex Martelli 的水禽(Python 引入抽象基类的原因)

  • 白鹅类型(goose typing), 只要 cls 是抽象基类, 即 cls 的元类是 abc.ABCMeta, 就可以使用 isinstance(obj, cls)
  • 优点:
    • 可以使用 register 类方法在终端用户的代码中把某个类"声明"为一个抽象基类的"虚拟"子类
  • Struggle 类实现了 __len__ 方法, abc.Sized 就能把 Struggle 识别为自己的子类

5. 定义抽象基类的子类

  • code:

      ::Python
      import collections
      Card = collections.namedtuple('Card', ['rank', 'suit'])
    
      class FrenchDeck2(collections.MutableSequence):
          ranks = [str(n) for n in range(2, 11)] + list('JQKA')
          suits = 'spades diamonds clubs hearts'.split()
    
          def __init__(self):
              self._cards = [Card(rank, suit) for suit in self.suits
                                              for rank in self.ranks]
          def __len__(self):
              return len(self._cards)
    
          def __getitem__(self, position):
              return self._cards[position]
    
          def __setitem__(self, position, value):
              self._cards[position] = value
    
          def __delitem__(self, position):
              del self._cards[position]
    
          def insert(self, position, value):
              self._cards.insert(position, value)
    
  • 导入时(加载并编译 frenchdeck2.py 时), Python 不会检查抽象方法的实现, 在运行实例化 FrenchDeck2 类时才会真正检查, 即便实现 FrenchDeck2 类不需要 __delitem__insert 抽象方法提供的行为, 也得实现, 因为 MutableSequence 抽象基类需要, 不然会报错

  • Sequence: __getitem__, __contains__, __iter__, __reversed__, index, count

  • MutableSequence: __setitem__, __delitem__, insert, append, reverse, extend, pop, remove, __iadd__

6. 标准库中的抽象基类

  1. collections.abc 模块中的抽象基类
    • Iterable 通过 __iter__ 方法支持迭代, Container 通过 __contains__ 方法支持 in 运算符 和 Sized 通过 __len__ 方法支持 len() 函数
    • Sequence, Mapping, Set
    • MappingVIew, Callable, Hashable
  2. 抽象基类的数字塔(numbers)
    • 检查一个数是不是整数, 可以使用 isinstance(x, numbers.Integral)

7. 定义并使用一个抽象基类

  1. 场景: 展示随机广告, 随机挑选无重复

    • 定义一个名为 Tombola 的抽象基类, 四个方法, 前两个为抽象方法

      • .load(…) 装入元素
      • .pick() 从容器中随机取出一个元素, 返回选中的元素
      • .loaded() 如果容器中至少有一个元素, 返回 True
      • .inspect() 返回一个有序元组, 由容器中的现有元素构成
    • tombola.py:

        ::Python
        import abc
        class Tombola(abc.ABC):
            @abc.abstractmethod
            def load(self, iterable):
                """从可迭代对象中添加元素"""
      
            @abc.abstractmethod
            def pick(self):
                """随机删除元素, 然后将其返回
                   如果实例为空, 这个方法应该抛出 `LookupError`
                """
      
            def loaded(self):
                """如果至少有一个元素, 返回 `True`, 否则返回 `False`"""
                return bool(self.inspect())
      
            def inspect(self):
                """返回一个有序元组, 由当前元素构成"""
                items = []
                while True:
                    try:
                        items.append(self.pick())
                    except LookupError:
                        break
                self.load(items)
                return tuple(sorted(items))
      
  2. 抽象基类句法详解

    • 声明抽象类方法的推荐方式: (装饰器顺序, @abstractmethdo 应该放在最里层)

        ::Python
        class MyABC(abc.ABC):
            @classmethod
            @abc.abstractmethod
            def an_abstract_classmethod(cls, ...):
                pass
      
  3. 定义 Tombola 抽象基类的子类

    • BingoCage 类实现了抽象方法 load 和 pick, 继承了 loaded 方法, 覆盖了 inspect 方法, 增加了 __call__ 方法

        ::Python
        import random
      
        class BingoCage(Tombola):
            def __init__(self, items):
                self._randomizer = random.SystemRandom()
                self._items = []
                self.load(items)
      
            def load(self, items):
                self._items.extend(items)
                self._randomizer.shuffle(self._items)
      
            def pick(self):
                try:
                    return self._items.pop()
                except IndexError:
                    raise LookupError('pick from empty BingoCage')
      
            def __call__(self):
                self.pick()
      
    • LotteryBlower 在 __init__ 方法中, self._balls 保存的是 list(iterable), list 会创建原对象的副本

        ::Python
        class LotteryBlower(Tombola):
            def __init__(self, iterable):
                self._balls = list(iterable) # list 创建参数的副本
      
            def load(self, iterable):
                self._balls.extend(iterable)
            def pick(self):
                try:
                    position = random.randrange(len(self._balls))
                except ValueError:
                    raise LookupError('pick from empty LotteryBlower')
                return self._balls.pop(position)
      
            def loaded():
                return bool(self._balls)
      
            def inspect(selef):
                return tuple(sorted(self._balls))
      
  4. Tombola 的虚拟子类

    1. 注册虚拟子类的方式是在抽象基类上调用 register 方法, 之后注册的类会变成抽象基类的虚拟子类, issubclass, isinstance 都能识别, 但是注册的类不会从抽象基类中继承任何方法或属性

    2. register 可以通过函数调用或者装饰器来使用

       ::Python
       @Tombola.register
       class TomboList(list):
           def pick(self):
               if self:
                   position = randrange(len(self))
                   return self.pop(position)
               else:
                   raise LookupError('pop from empty TomboList')            
      
           load = list.extend
      
           def loaded(self):
               return bool(self)
           def inspect(self):
               return tuple(sorted(self))
      
       >>> issubclass(TomboList, Tombola)
       True
       >>> t = TomboList(range(100))
       >>> isinstance(t, Tombola)
       True
       >>> TomboList.__mro__
       (<class 'tombolist.TomboList'>, <class 'list'>, <class 'object'>)
      
    3. Tombolist.__mro__ 中只有真实的超类, 没有 Tombola, 所以Tombolist 没有从 Tombola 中继承任何方法

8. Tombola 子类的测试方法

pass

9. Python 使用 register 的方式

  • 类装饰器, 普通函数调用,

10. 鹅的行为有可能像鸭子

  • 实现__len__ 方法就会归为 abc.Sized 的子类, 这是因为 abc.Sized 实现了一个特殊的类方法 __subclasshook__

11.本章小结

  • 如果一个类实现了 __getitem__ 方法, 就可以实现迭代和 in 操作

延伸阅读

  • PEP 484, 类型提示

  • 如果一门语言很少隐式转换类型, 说明它是强类型语言, 如果经常这么做, 那么他是弱类型语言

  • 强类型语言: Java, C++, Python

  • 弱类型语言: PHP, JavaScript, Perl

  • 静态类型: 在编译时检查类型的语言是静态类型语言 Fortran

  • 动态类型: 在运行时检查类型的语言是动态类型语言 Lisp

  • JavaScript:

      ::JavaScript
      '' == '0'   // false
      0 == ''     // true
      0 == '0'    // true
      '' < 0      // false
      '' < '0'    // true
    
  • python 不会自动在字符串和数字之间强制转换, 所以 Python3 上述表达式的结果 == 都是 False, < 会抛出 TypeError

  • 故 Python 是动态强类型语言

  • 接口

    • Java 添加 interface 语言结果, 允许一个类实现多个接口
    • Go 不支持继承, 与 Python 相比, Go 相当于 每个抽象基类都实现了 __subclasshook__ 方法

Chapter12 继承的优缺点

0. 本章要点

  • 子类化内置类型的缺点
  • 多重继承和方法解析顺序
  • Java 不支持多重继承, C++ 容易滥用多重继承

1. 子类化内置类型很麻烦

  • 内置类型 dict 的 __init____update__ 会忽略子类覆盖的 __setitem__ 方法

      ::Python
      In [62]: class DoppelDict(dict):
          ...:     def __setitem__(self, key, value):
          ...:         super().__setitem__(key, [value]*2)
          ...:
    
      In [63]: dd = DoppelDict(one=1)
      In [64]: dd # 'one' 值没有重复
      Out[64]: {'one': 1}
      In [65]: dd['two'] = 2
      In [66]: dd
      Out[66]: {'one': 1, 'two': [2, 2]}
      In [67]: dd.update(three=3)
      In [68]: dd # update 方法也没有使用 `__setitem__`
      Out[68]: {'one': 1, 'three': 3, 'two': [2, 2]}
    
  • dict.update 方法会忽略 AnswerDict.__getitem__ 方法

      ::Python
      In [69]: class AnswerDict(dict):
          ...:     def __getitem__(self, key):
          ...:         return 42
          ...:
      In [70]: ad = AnswerDict(a='foo')
      Out[71]: 42
      In [72]: d = {}
      In [73]: d.update(ad)
      In [74]: d
      Out[74]: {'a': 'foo'}
      In [75]: d['a']
      Out[75]: 'foo'
      In [76]: ad
    
  • 结论: 不要子类化内置类型, 直接子类化内置类型(dict, list, str) 会容易出错, 此类问题只发生在 C 语言实现的内置类型内部的方法委托上, 而且只影响直接继承内置类型的用户自定义类

  • 用户自定义类应该继承 collections 模块

      ::Python
      In [77]: import collections
      In [80]: class DoppelDict2(collections.UserDict):
          ...:     def __setitem__(self, key, value):
          ...:         super().__setitem__(key, [value]*2)
          ...:
    
      In [81]: dd = DoppelDict2(one=1)
      In [82]: dd
      Out[82]: {'one': [1, 1]}
      In [83]: dd.update(three=3)
      In [84]: dd
      Out[84]: {'one': [1, 1], 'three': [3, 3]}
    

2. 多重继承和方法解析顺序

  • 菱形问题, 不同父类, 同名方法

      ::Python
      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)
      d = D()
      d.pong() # 调用的是 B 类的方法
      # pong: <__main__.D object at 0x110445d68>
      C.pong(d)
      # PONG: <__main__.D object at 0x110445d68>
    
  • 继承父类同名方法的调用顺序: 方法发解析顺序(Method Resolution Order, MRO)

    • __mro__ 属性, 值为元组 上例中 D.__mro__ 的值为: (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>), 即调用顺序为 D -> B -> C -> A -> object

    • 指定调用某一超类:

        ::Python
        def ping(self):
            A.ping(self)  # 而不是 super().ping()
            print('post-ping:', self)
      
    • 几个类的 mro

        ::Python
        In [86]: bool.__mro__
        Out[86]: (bool, int, object)
      
        In [87]: import io
      
        In [88]: io.BytesIO.__mro__
        Out[88]: (_io.BytesIO, _io._BufferedIOBase, _io._IOBase, object)
      
    • Tkingter 的多重继承, 图(TODO):

        ::Python
        In [89]: import tkinter
        In [90]: tkinter.Text.__mro__
        Out[90]:
        (tkinter.Text,
         tkinter.Widget,
         tkinter.BaseWidget,
         tkinter.Misc,
         tkinter.Pack,
         tkinter.Place,
         tkinter.Grid,
         tkinter.XView,
         tkinter.YView,
         object)
      

3. 多重继承的真实应用(Tkinter)(TODO)

4. 处理多重继承的建议

  1. 把接口继承和实现继承区分开

    • 创建子类的需求:
      • 继承接口, 创建子类型, 实现"是什么"关系
      • 继承实现, 通过重用避免重复代码
  2. 使用抽象基类显示表示接口

  3. 通过混入重用代码(mixin class)

    • 混入类不能实例化
    • 具体类不能只继承混入类
  4. 在名称中明确指明混入

  5. 抽象基类可以作为混入, 但混入类不能作为抽象基类

    • 抽象类的局限: 抽象类中实现的具体方法只能与抽象基类及其超类中的方法协作
  6. 不要子类化多个具体类, 以下代码中 如果 Alpha 是具体类, nameBeta 和 Gamma 必须是抽象基类或混入

     ::Python
     class MyConcreteClass(Alpha, Beta, Gamma):
         ...
    
  7. 为用户提供聚合类(aggregate class), 以下代码中, Widget 类定义体是空的, 作用是把四个超类结合在一起, 同样的例子是 Django 中的 ListView 类

     ::Python
     class Widget(BaseWidget, Pack, Place, Grid):
         ''' Internal classl
         Base class for a widget which can be positioned with the geometry managers Pack, Place or Grid.'''
         pass
    
  8. 优先使用对象组合, 而不是类继承

  9. 分析 Tkinter (TODO)

5. Django 通用视图混入类(GenericView, Mixin)

  1. Django Class-Based Views, Django 类介绍, django.views.generic.base 模块的 UML 类图
  2. view 是所有视图类的基类, 核心功能:(as_view, dispatch, http_method_not_allowed, options), RedirectView 类只继承 View, 实现了 get, head, post等方法
  3. Django 的分派机制是动态版模板方法模式, 即 View 类不强制子类实现所有处理方法, 而是让 dispatch 方法在运行时检查有没有针对特定请求的具体处理方法, 例子: TemplateView 类只用于显示内容, 只实现了 get 方法, 如果 把 POST 请求发给 TemplateView, 经继承的 View.dispatch 方法检查没有 post 方法, 就会方法 405 Method Not Allowed
  4. ListView, 是一个聚合类, 不含有代码

Chapter13 正确重载运算符 TODO###

0. 本章要点