├── .gitignore ├── LICENSE ├── README.md ├── markdown ├── 第01章:Python 数据类型.md ├── 第02章:序列构成的数组.md ├── 第03章:字典和集合.md ├── 第04章:文本与字节序列.md ├── 第05章:一等函数.md ├── 第06章:使用一等函数实现设计模式.md ├── 第07章:函数装饰器与闭包.md ├── 第08章:对象引用、可变性和垃圾回收.md ├── 第09章:符合Python风格的对象.md ├── 第10章:序列的修改、散列和切片.md ├── 第11章:接口:从协议到抽象基类.md ├── 第12章:继承的优缺点.md ├── 第13章:正确重载运算符.md ├── 第14章:可迭代的对象、迭代器和生成器.md ├── 第15章:上下文管理器和else块.md ├── 第16章:协程.md ├── 第17章:使用期物处理并发.md ├── 第18章:使用asyncio包处理并发.md ├── 第19章:动态属性和特性.md ├── 第20章:属性描述符.md └── 第21章:类元编程.md ├── 第01章:Python 数据类型.ipynb ├── 第02章:序列构成的数组.ipynb ├── 第03章:字典和集合.ipynb ├── 第04章:文本与字节序列.ipynb ├── 第05章:一等函数.ipynb ├── 第06章:使用一等函数实现设计模式.ipynb ├── 第07章:函数装饰器与闭包.ipynb ├── 第08章:对象引用、可变性和垃圾回收.ipynb ├── 第09章:符合Python风格的对象.ipynb ├── 第10章:序列的修改、散列和切片.ipynb ├── 第11章:接口:从协议到抽象基类.ipynb ├── 第12章:继承的优缺点.ipynb ├── 第13章:正确重载运算符.ipynb ├── 第14章:可迭代的对象、迭代器和生成器.ipynb ├── 第15章:上下文管理器和else块.ipynb ├── 第16章:协程.ipynb ├── 第17章:使用期物处理并发.ipynb ├── 第18章:使用asyncio包处理并发.ipynb ├── 第19章:动态属性和特性.ipynb ├── 第20章:属性描述符.ipynb └── 第21章:类元编程.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 《流畅的 Python》阅读笔记 2 | 本 Repo 是我在阅读[《流畅的 Python》](http://www.ituring.com.cn/book/1564)过程中为了辅助记忆所做的笔记。 3 | 4 | 为了方便地在笔记中运行 Python 代码,所以我使用 [Jupyter Notebook](https://jupyter.org/) 来作为笔记载体。 5 | 如果希望在线预览,可以考虑访问 [markdown](markdown/) 目录;如果希望编辑并运行笔记中的代码,可以考虑安装 `jupyter` 后在本地运行。 6 | 7 | # 许可 8 | [![知识共享许可协议](https://i.creativecommons.org/l/by-sa/4.0/88x31.png)](http://creativecommons.org/licenses/by-sa/4.0/) 9 | 本作品采用[知识共享署名-相同方式共享 4.0 国际许可协议](http://creativecommons.org/licenses/by-sa/4.0/)进行许可。 -------------------------------------------------------------------------------- /markdown/第01章:Python 数据类型.md: -------------------------------------------------------------------------------- 1 | 2 | # Python 数据类型 3 | > Guido 对语言设计美学的深入理解让人震惊。我认识不少很不错的编程语言设计者,他们设计出来的东西确实很精彩,但是从来都不会有用户。Guido 知道如何在理论上做出一定妥协,设计出来的语言让使用者觉得如沐春风,这真是不可多得。 4 | > ——Jim Hugunin 5 | > Jython 的作者,AspectJ 的作者之一,.NET DLR 架构师 6 | 7 | Python 最好的品质之一是**一致性**:你可以轻松理解 Python 语言,并通过 Python 的语言特性在类上定义**规范的接口**,来支持 Python 的核心语言特性,从而写出具有“Python 风格”的对象。 8 | Python 解释器在碰到特殊的句法时,会使用特殊方法(我们称之为魔术方法)去激活一些基本的对象操作。如 `my_c[key]` 语句执行时,就会调用 `my_c.__getitem__` 函数。这些特殊方法名能让你自己的对象实现和支持一下的语言构架,并与之交互: 9 | * 迭代 10 | * 集合类 11 | * 属性访问 12 | * 运算符重载 13 | * 函数和方法的调用 14 | * 对象的创建和销毁 15 | * 字符串表示形式和格式化 16 | * 管理上下文(即 `with` 块) 17 | 18 | 19 | ```python 20 | # 通过实现魔术方法,来让内置函数支持你的自定义对象 21 | # https://github.com/fluentpython/example-code/blob/master/01-data-model/frenchdeck.py 22 | import collections 23 | import random 24 | 25 | Card = collections.namedtuple('Card', ['rank', 'suit']) 26 | 27 | class FrenchDeck: 28 | ranks = [str(n) for n in range(2, 11)] + list('JQKA') 29 | suits = 'spades diamonds clubs hearts'.split() 30 | 31 | def __init__(self): 32 | self._cards = [Card(rank, suit) for suit in self.suits 33 | for rank in self.ranks] 34 | 35 | def __len__(self): 36 | return len(self._cards) 37 | 38 | def __getitem__(self, position): 39 | return self._cards[position] 40 | 41 | deck = FrenchDeck() 42 | # 实现 __length__ 以支持 len 43 | print(len(deck)) 44 | # 实现 __getitem__ 以支持下标操作 45 | print(deck[1]) 46 | print(deck[5::13]) 47 | # 有了这些操作,我们就可以直接对这些对象使用 Python 的自带函数了 48 | print(random.choice(deck)) 49 | ``` 50 | 51 | 52 52 | Card(rank='3', suit='spades') [Card(rank='7', suit='spades'), Card(rank='7', suit='diamonds'), Card(rank='7', suit='clubs'), Card(rank='7', suit='hearts')] 53 | Card(rank='6', suit='diamonds') 54 | 55 | 56 | Python 支持的所有魔术方法,可以参见 Python 文档 [Data Model](https://docs.python.org/3/reference/datamodel.html) 部分。 57 | 58 | 比较重要的一点:不要把 `len`,`str` 等看成一个 Python 普通方法:由于这些操作的频繁程度非常高,所以 Python 对这些方法做了特殊的实现:它可以让 Python 的内置数据结构走后门以提高效率;但对于自定义的数据结构,又可以在对象上使用通用的接口来完成相应工作。但在代码编写者看来,`len(deck)` 和 `len([1,2,3])` 两个实现可能差之千里的操作,在 Python 语法层面上是高度一致的。 59 | -------------------------------------------------------------------------------- /markdown/第02章:序列构成的数组.md: -------------------------------------------------------------------------------- 1 | 2 | # 序列构成的数组 3 | > 你可能注意到了,之前提到的几个操作可以无差别地应用于文本、列表和表格上。 4 | > 我们把文本、列表和表格叫作数据火车……FOR 命令通常能作用于数据火车上。 5 | > ——Geurts、Meertens 和 Pemberton 6 | > *ABC Programmer’s Handbook* 7 | 8 | * 容器序列 9 | `list`、`tuple` 和 `collections.deque` 这些序列能存放不同类型的数据。 10 | * 扁平序列 11 | `str`、`bytes`、`bytearray`、`memoryview` 和 `array.array`,这类序列只能容纳一种类型。 12 | 13 | 容器序列存放的是它们所包含的任意类型的对象的**引用**,而扁平序列里存放的**是值而不是引用**。换句话说,扁平序列其实是一段连续的内存空间。由此可见扁平序列其实更加紧凑,但是它里面只能存放诸如字符、字节和数值这种基础类型。 14 | 15 | 序列类型还能按照能否被修改来分类。 16 | * 可变序列 17 | `list`、`bytearray`、`array.array`、`collections.deque` 和 `memoryview`。 18 | * 不可变序列 19 | `tuple`、`str` 和 `bytes` 20 | 21 | 22 | ```python 23 | # 列表推导式和生成器表达式 24 | symbols = "列表推导式" 25 | [ord(symbol) for symbol in symbols] 26 | (ord(symbol) for symbol in symbols) 27 | ``` 28 | 29 | 30 | ```python 31 | # 因为 pack/unpack 的存在,元组中的元素会凸显出它们的位置信息 32 | first, *others, last = (1, 2, 3, 4, 5) 33 | print(first, others, last) 34 | # 当然后面很多可迭代对象都支持 unpack 了… 35 | ``` 36 | 37 | 38 | ```python 39 | # namedtuple 40 | from collections import namedtuple 41 | 42 | Point = namedtuple('Point', ['x', 'y']) 43 | p = Point(1, 2) 44 | print(p, p.x, p.y) 45 | # _asdict() 会返回 OrderedDict 46 | print(p._asdict()) 47 | ``` 48 | 49 | 50 | ```python 51 | # 为什么切片(slice)不返回最后一个元素 52 | a = list(range(6)) 53 | # 使用同一个数即可将列表进行分割 54 | print(a[:2], a[2:]) 55 | ``` 56 | 57 | 58 | ```python 59 | # Ellipsis 60 | def test(first, xxx, last): 61 | print(xxx) 62 | print(type(xxx)) 63 | print(xxx == ...) 64 | print(xxx is ...) 65 | return first, last 66 | 67 | # ... 跟 None 一样,有点神奇 68 | print(test(1, ..., 2)) 69 | ``` 70 | 71 | ### bisect 二分查找 72 | 73 | 74 | ```python 75 | import bisect 76 | def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'): 77 | i = bisect.bisect(breakpoints, score) 78 | return grades[i] 79 | 80 | print([grade(score) for score in [33, 99, 77, 70, 89, 90, 100]]) 81 | 82 | a = list(range(0, 100, 10)) 83 | # 插入并保持有序 84 | bisect.insort(a, 55) 85 | print(a) 86 | ``` 87 | 88 | ### Array 89 | > 虽然列表既灵活又简单,但面对各类需求时,我们可能会有更好的选择。比如,要存放 1000 万个浮点数的话,数组(array)的效率要高得多,因为数组在背后存的并不是 float 对象,而是数字的机器翻译,也就是字节表述。这一点就跟 C 语言中的数组一样。再比如说,如果需要频繁对序列做先进先出的操作,deque(双端队列)的速度应该会更快。 90 | 91 | `array.tofile` 和 `fromfile` 可以将数组以二进制格式写入文件,速度要比写入文本文件快很多,文件的体积也小。 92 | 93 | > 另外一个快速序列化数字类型的方法是使用 pickle(https://docs.python.org/3/library/pickle.html)模块。pickle.dump 处理浮点数组的速度几乎跟array.tofile 一样快。不过前者可以处理几乎所有的内置数字类型,包含复数、嵌套集合,甚至用户自定义的类。前提是这些类没有什么特别复杂的实现。 94 | 95 | array 具有 `type code` 来表示数组类型:具体可见 [array 文档](https://docs.python.org/3/library/array.html). 96 | 97 | ### memoryview 98 | > memoryview.cast 的概念跟数组模块类似,能用不同的方式读写同一块内存数据,而且内容字节不会随意移动。 99 | 100 | 101 | ```python 102 | import array 103 | 104 | arr = array.array('h', [1, 2, 3]) 105 | memv_arr = memoryview(arr) 106 | # 把 signed short 的内存使用 char 来呈现 107 | memv_char = memv_arr.cast('B') 108 | print('Short', memv_arr.tolist()) 109 | print('Char', memv_char.tolist()) 110 | memv_char[1] = 2 # 更改 array 第一个数的高位字节 111 | # 0x1000000001 112 | print(memv_arr.tolist(), arr) 113 | print('-' * 10) 114 | bytestr = b'123' 115 | # bytes 是不允许更改的 116 | try: 117 | bytestr[1] = '3' 118 | except TypeError as e: 119 | print(repr(e)) 120 | memv_byte = memoryview(bytestr) 121 | print('Memv_byte', memv_byte.tolist()) 122 | # 同样这块内存也是只读的 123 | try: 124 | memv_byte[1] = 1 125 | except TypeError as e: 126 | print(repr(e)) 127 | 128 | ``` 129 | 130 | ### Deque 131 | `collections.deque` 是比 `list` 效率更高,且**线程安全**的双向队列实现。 132 | 133 | 除了 collections 以外,以下 Python 标准库也有对队列的实现: 134 | * queue.Queue (可用于线程间通信) 135 | * multiprocessing.Queue (可用于进程间通信) 136 | * asyncio.Queue 137 | * heapq 138 | -------------------------------------------------------------------------------- /markdown/第03章:字典和集合.md: -------------------------------------------------------------------------------- 1 | 2 | # 字典和集合 3 | 4 | > 字典这个数据结构活跃在所有 Python 程序的背后,即便你的源码里并没有直接用到它。 5 | > ——A. M. Kuchling 6 | 7 | 可散列对象需要实现 `__hash__` 和 `__eq__` 函数。 8 | 如果两个可散列对象是相等的,那么它们的散列值一定是一样的。 9 | 10 | 11 | ```python 12 | # 字典提供了很多种构造方法 13 | a = dict(one=1, two=2, three=3) 14 | b = {'one': 1, 'two': 2, 'three': 3} 15 | c = dict(zip(['one', 'two', 'three'], [1, 2, 3])) 16 | d = dict([('two', 2), ('one', 1), ('three', 3)]) 17 | e = dict({'three': 3, 'one': 1, 'two': 2}) 18 | a == b == c == d == e 19 | ``` 20 | 21 | 22 | ```python 23 | # 字典推导式 24 | r = range(5) 25 | d = {n * 2: n for n in r if n < 3} 26 | print(d) 27 | # setdefault 28 | for n in r: 29 | d.setdefault(n, 0) 30 | print(d) 31 | ``` 32 | 33 | 34 | ```python 35 | # defaultdcit & __missing__ 36 | class mydefaultdict(dict): 37 | def __init__(self, value, value_factory): 38 | super().__init__(value) 39 | self._value_factory = value_factory 40 | 41 | def __missing__(self, key): 42 | # 要避免循环调用 43 | # return self[key] 44 | self[key] = self._value_factory() 45 | return self[key] 46 | 47 | d = mydefaultdict({1:1}, list) 48 | print(d[1]) 49 | print(d[2]) 50 | d[3].append(1) 51 | print(d) 52 | ``` 53 | 54 | ### 字典的变种 55 | * collections.OrderedDict 56 | * collections.ChainMap (容纳多个不同的映射对象,然后在进行键查找操作时会从前到后逐一查找,直到被找到为止) 57 | * collections.Counter 58 | * colllections.UserDict (dict 的 纯 Python 实现) 59 | 60 | 61 | ```python 62 | # UserDict 63 | # 定制化字典时,尽量继承 UserDict 而不是 dict 64 | from collections import UserDict 65 | 66 | class mydict(UserDict): 67 | def __getitem__(self, key): 68 | print('Getting key', key) 69 | return super().__getitem__(key) 70 | 71 | d = mydict({1:1}) 72 | print(d[1], d[2]) 73 | ``` 74 | 75 | 76 | ```python 77 | # MyppingProxyType 用于构建 Mapping 的只读实例 78 | from types import MappingProxyType 79 | 80 | d = {1: 1} 81 | d_proxy = MappingProxyType(d) 82 | print(d_proxy[1]) 83 | try: 84 | d_proxy[1] = 1 85 | except Exception as e: 86 | print(repr(e)) 87 | 88 | d[1] = 2 89 | print(d_proxy[1]) 90 | ``` 91 | 92 | 93 | ```python 94 | # set 的操作 95 | # 子集 & 真子集 96 | a, b = {1, 2}, {1, 2} 97 | print(a <= b, a < b) 98 | 99 | # discard 100 | a = {1, 2, 3} 101 | a.discard(3) 102 | print(a) 103 | 104 | # pop 105 | print(a.pop(), a.pop()) 106 | try: 107 | a.pop() 108 | except Exception as e: 109 | print(repr(e)) 110 | ``` 111 | 112 | ### 集合字面量 113 | 除空集之外,集合的字面量——`{1}`、`{1, 2}`,等等——看起来跟它的数学形式一模一样。**如果是空集,那么必须写成 `set()` 的形式**,否则它会变成一个 `dict`. 114 | 跟 `list` 一样,字面量句法会比 `set` 构造方法要更快且更易读。 115 | 116 | ### 集合和字典的实现 117 | 集合和字典采用散列表来实现: 118 | 1. 先计算 key 的 `hash`, 根据 hash 的某几位(取决于散列表的大小)找到元素后,将该元素与 key 进行比较 119 | 2. 若两元素相等,则命中 120 | 3. 若两元素不等,则发生散列冲突,使用线性探测再散列法进行下一次查询。 121 | 122 | 这样导致的后果: 123 | 1. 可散列对象必须支持 `hash` 函数; 124 | 2. 必须支持 `__eq__` 判断相等性; 125 | 3. 若 `a == b`, 则必须有 `hash(a) == hash(b)`。 126 | 127 | 注:所有由用户自定义的对象都是可散列的,因为他们的散列值由 id() 来获取,而且它们都是不相等的。 128 | 129 | 130 | ### 字典的空间开销 131 | 由于字典使用散列表实现,所以字典的空间效率低下。使用 `tuple` 代替 `dict` 可以有效降低空间消费。 132 | 不过:内存太便宜了,不到万不得已也不要开始考虑这种优化方式,**因为优化往往是可维护性的对立面**。 133 | 134 | 往字典中添加键时,如果有散列表扩张的情况发生,则已有键的顺序也会发生改变。所以,**不应该在迭代字典的过程各种对字典进行更改**。 135 | 136 | 137 | ```python 138 | # 字典中就键的顺序取决于添加顺序 139 | 140 | keys = [1, 2, 3] 141 | dict_ = {} 142 | for key in keys: 143 | dict_[key] = None 144 | 145 | for key, dict_key in zip(keys, dict_): 146 | print(key, dict_key) 147 | assert key == dict_key 148 | 149 | # 字典中键的顺序不会影响字典比较 150 | ``` 151 | -------------------------------------------------------------------------------- /markdown/第04章:文本与字节序列.md: -------------------------------------------------------------------------------- 1 | 2 | # 文本和字节序列 3 | 4 | > 人类使用文本,计算机使用字节序列 5 | > —— Esther Nam 和 Travis Fischer "Character Encoding and Unicode in Python" 6 | 7 | Python 3 明确区分了人类可读的文本字符串和原始的字节序列。 8 | 隐式地把字节序列转换成 Unicode 文本(的行为)已成过去。 9 | 10 | ### 字符与编码 11 | 字符的标识,及**码位**,是 0~1114111 的数字,在 Unicode 标准中用 4-6 个十六进制数字表示,如 A 为 U+0041, 高音谱号为 U+1D11E,😂 为 U+1F602. 12 | 字符的具体表述取决于所用的**编码**。编码时在码位与字节序列自减转换时使用的算法。 13 | 把码位转换成字节序列的过程是**编码**,把字节序列转成码位的过程是**解码**。 14 | 15 | ### 序列类型 16 | Python 内置了两种基本的二进制序列类型:不可变的 `bytes` 和可变的 `bytearray` 17 | 18 | 19 | ```python 20 | # 基本的编码 21 | content = "São Paulo" 22 | for codec in ["utf_8", "utf_16"]: 23 | print(codec, content.encode(codec)) 24 | 25 | # UnicodeEncodeError 26 | try: 27 | content.encode('cp437') 28 | except UnicodeEncodeError as e: 29 | print(e) 30 | 31 | # 忽略无法编码的字符 32 | print(content.encode('cp437', errors='ignore')) 33 | # 把无法编码的字符替换成 ? 34 | print(content.encode('cp437', errors='replace')) 35 | # 把无法编码的字符替换成 xml 实体 36 | print(content.encode('cp437', errors='xmlcharrefreplace')) 37 | 38 | # 还可以自己设置错误处理方式 39 | # https://docs.python.org/3/library/codecs.html#codecs.register_error 40 | ``` 41 | 42 | utf_8 b'S\xc3\xa3o Paulo' 43 | utf_16 b'\xff\xfeS\x00\xe3\x00o\x00 \x00P\x00a\x00u\x00l\x00o\x00' 44 | 'charmap' codec can't encode character '\xe3' in position 1: character maps to 45 | b'So Paulo' 46 | b'S?o Paulo' 47 | b'São Paulo' 48 | 49 | 50 | 51 | ```python 52 | # 基本的解码 53 | # 处理 UnicodeDecodeError 54 | octets = b'Montr\xe9al' 55 | print(octets.decode('cp1252')) 56 | print(octets.decode('iso8859_7')) 57 | print(octets.decode('koi8_r')) 58 | try: 59 | print(octets.decode('utf-8')) 60 | except UnicodeDecodeError as e: 61 | print(e) 62 | 63 | # 将错误字符替换成 � (U+FFFD) 64 | octets.decode('utf-8', errors='replace') 65 | ``` 66 | 67 | 68 | ```python 69 | # Python3 可以使用非 ASCII 名称 70 | São = 'Paulo' 71 | # 但是不能用 Emoji… 72 | ``` 73 | 74 | 可以用 `chardet` 检测字符所使用的编码 75 | 76 | BOM:字节序标记 (byte-order mark): 77 | `\ufffe` 为字节序标记,放在文件开头,UTF-16 用它来表示文本以大端表示(`\xfe\xff`)还是小端表示(`\xff\xfe`)。 78 | UTF-8 编码并不需要 BOM,但是微软还是给它加了 BOM,非常烦人。 79 | 80 | ### 处理文本文件 81 | 处理文本文件的最佳实践是“三明治”:要尽早地把输入的字节序列解码成字符串,尽量晚地对字符串进行编码输出;在处理逻辑中只处理字符串对象,不应该去编码或解码。 82 | 除非想判断编码,否则不要再二进制模式中打开文本文件;即便如此,也应该使用 `Chardet`,而不是重新发明轮子。 83 | 常规代码只应该使用二进制模式打开二进制文件,比如图像。 84 | 85 | ### 默认编码 86 | 可以使用 `sys.getdefaultincoding()` 获取系统默认编码; 87 | Linux 的默认编码为 `UTF-8`,Windows 系统中不同语言设置使用的编码也不同,这导致了更多的问题。 88 | `locale.getpreferredencoding()` 返回的编码是最重要的:这是打开文件的默认编码,也是重定向到文件的 `sys.stdout/stdin/stderr` 的默认编码。不过这个编码在某些系统中是可以改的… 89 | 所以,关于编码默认值的最佳建议是:别依赖默认值。 90 | 91 | ### Unicode 编码方案 92 | ```python 93 | a = 'café' 94 | b = 'cafe\u0301' 95 | print(a, b) # café café 96 | print(ascii(a), ascii(b)) # 'caf\xe9' 'cafe\u0301' 97 | print(len(a), len(b), a == b) # 4 5 False 98 | ``` 99 | 100 | 在 Unicode 标准中,é 和 e\u0301 这样的序列叫“标准等价物”,应用程序应将它视为相同的字符。但 Python 看到的是不同的码位序列,因此判断两者不相同。 101 | 我们可以用 `unicodedata.normalize` 将 Unicode 字符串规范化。有四种规范方式:NFC, NFD, NFKC, NFKD 102 | 103 | NFC 使用最少的码位构成等价的字符串,而 NFD 会把组合字符分解成基字符和单独的组合字符。 104 | NFKC 和 NFKD 是出于兼容性考虑,在分解时会将字符替换成“兼容字符”,这种情况下会有格式损失。 105 | 兼容性方案可能会损失或曲解信息(如 "4²" 会被转换成 "42"),但可以为搜索和索引提供便利的中间表述。 106 | 107 | > 使用 NFKC 和 NFKC 规范化形式时要小心,而且只能在特殊情况中使用,例如搜索和索引,而不能用户持久存储,因为这两种转换会导致数据损失。 108 | 109 | 110 | ```python 111 | from unicodedata import normalize, name 112 | # Unicode 码位 113 | a = 'café' 114 | b = 'cafe\u0301' 115 | print(a, b) 116 | print(ascii(a), ascii(b)) 117 | print(len(a), len(b), a == b) 118 | 119 | ## NFC 和 NFD 120 | print(len(normalize('NFC', a)), len(normalize('NFC', b))) 121 | print(len(normalize('NFD', a)), len(normalize('NFD', b))) 122 | print(len(normalize('NFC', a)) == len(normalize('NFC', b))) 123 | 124 | print('-' * 15) 125 | # NFKC & NFKD 126 | s = '\u00bd' 127 | l = [s, normalize('NFKC', s), normalize('NFKD', s)] 128 | print(*l) 129 | print(*map(ascii, l)) 130 | micro = 'μ' 131 | l = [s, normalize('NFKC', micro)] 132 | print(*l) 133 | print(*map(ascii, l)) 134 | print(*map(name, l), sep='; ') 135 | ``` 136 | 137 | ### Unicode 数据库 138 | `unicodedata` 库中提供了很多关于 Unicode 的操作及判断功能,比如查看字符名称的 `name`,判断数字大小的 `numric` 等。 139 | 文档见 . 140 | 141 | 142 | ```python 143 | import unicodedata 144 | print(unicodedata.name('½')) 145 | print(unicodedata.numeric('½'), unicodedata.numeric('卅')) 146 | ``` 147 | 148 | VULGAR FRACTION ONE HALF 149 | 0.5 30.0 150 | 151 | 152 | 153 | ```python 154 | # 处理鬼符:按字节序将无法处理的字节序列依序替换成 \udc00 - \udcff 之间的码位 155 | x = 'digits-of-π' 156 | s = x.encode('gb2312') 157 | print(s) # b'digits-of-\xa6\xd0' 158 | ascii_err = s.decode('ascii', 'surrogateescape') 159 | print(ascii_err) # 'digits-of-\udca6\udcd0' 160 | print(ascii_err.encode('ascii', 'surrogateescape')) # b'digits-of-\xa6\xd0' 161 | ``` 162 | -------------------------------------------------------------------------------- /markdown/第05章:一等函数.md: -------------------------------------------------------------------------------- 1 | 2 | # 一等函数 3 | 4 | > 不管别人怎么说或怎么想,我从未觉得 Python 受到来自函数式语言的太多影响。我非常熟悉命令式语言,如 C 和 Algol 68,虽然我把函数定为一等对象,但是我并不把 Python 当作函数式编程语言。 5 | > —— Guido van Rossum: Python 仁慈的独裁者 6 | 7 | 在 Python 中,函数是一等对象。 8 | 编程语言理论家把“一等对象”定义为满足下述条件的程序实体: 9 | * 在运行时创建 10 | * 能赋值给变量或数据结构中的元素 11 | * 能作为参数传给函数 12 | * 能作为函数的返回结果 13 | 14 | 15 | ```python 16 | # 高阶函数:有了一等函数(作为一等对象的函数),就可以使用函数式风格编程 17 | fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana'] 18 | print(list(sorted(fruits, key=len))) # 函数 len 成为了一个参数 19 | 20 | # lambda 函数 & map 21 | fact = lambda x: 1 if x == 0 else x * fact(x-1) 22 | print(list(map(fact, range(6)))) 23 | 24 | # reduce 25 | from functools import reduce 26 | from operator import add 27 | print(reduce(add, range(101))) 28 | 29 | # all & any 30 | x = [0, 1] 31 | print(all(x), any(x)) 32 | ``` 33 | 34 | ['fig', 'apple', 'cherry', 'banana', 'raspberry', 'strawberry'] 35 | [1, 1, 2, 6, 24, 120] 36 | 5050 37 | False True 38 | 39 | 40 | Python 的可调用对象 41 | * 用户定义的函数:使用 `def` 或 `lambda` 创建 42 | * 内置函数:如 `len` 或 `time.strfttime` 43 | * 内置方法:如 `dict.get`(没懂这俩有什么区别…是说这个函数作为对象属性出现吗?) 44 | * 类:先调用 `__new__` 创建实例,再对实例运行 `__init__` 方法 45 | * 类的实例:如果类上定义了 `__call__` 方法,则实例可以作为函数调用 46 | * 生成器函数:调用生成器函数会返回生成器对象 47 | 48 | 49 | ```python 50 | # 获取函数中的信息 51 | # 仅限关键词参数 52 | def f(a, *, b): 53 | print(a, b) 54 | f(1, b=2) 55 | 56 | # 获取函数的默认参数 57 | # 原生的方法 58 | def f(a, b=1, *, c, d=3): 59 | pass 60 | 61 | def parse_defaults(func): 62 | code = func.__code__ 63 | argcount = code.co_argcount # 2 64 | varnames = code.co_varnames # ('a', 'b', 'c', 'd') 65 | argdefaults = dict(zip(reversed(varnames[:argcount]), func.__defaults__)) 66 | kwargdefaults = func.__kwdefaults__ 67 | return argdefaults, kwargdefaults 68 | 69 | print(*parse_defaults(f)) 70 | print('-----') 71 | # 看起来很麻烦,可以使用 inspect 模块 72 | from inspect import signature 73 | sig = signature(f) 74 | print(str(sig)) 75 | for name, param in sig.parameters.items(): 76 | print(param.kind, ':', name, "=", param.default) 77 | print('-----') 78 | # signature.bind 可以在不真正运行函数的情况下进行参数检查 79 | args = sig.bind(1, b=5, c=4) 80 | print(args) 81 | args.apply_defaults() 82 | print(args) 83 | ``` 84 | 85 | 1 2 86 | {'b': 1} {'d': 3} 87 | ----- 88 | (a, b=1, *, c, d=3) 89 | POSITIONAL_OR_KEYWORD : a = 90 | POSITIONAL_OR_KEYWORD : b = 1 91 | KEYWORD_ONLY : c = 92 | KEYWORD_ONLY : d = 3 93 | ----- 94 | 95 | 96 | 97 | 98 | 99 | ```python 100 | # 函数注解 101 | def clip(text: str, max_len: 'int > 0'=80) -> str: 102 | pass 103 | 104 | from inspect import signature 105 | sig = signature(clip) 106 | print(sig.return_annotation) 107 | for param in sig.parameters.values(): 108 | note = repr(param.annotation).ljust(13) 109 | print("{note:13} : {name} = {default}".format( 110 | note=repr(param.annotation), name=param.name, 111 | default=param.default)) 112 | ``` 113 | 114 | 115 | : text = 116 | 'int > 0' : max_len = 80 117 | 118 | 119 | #### 支持函数式编程的包 120 | `operator` 里有很多函数,对应着 Python 中的内置运算符,使用它们可以避免编写很多无趣的 `lambda` 函数,如: 121 | * `add`: `lambda a, b: a + b` 122 | * `or_`: `lambda a, b: a or b` 123 | * `itemgetter`: `lambda a, b: a[b]` 124 | * `attrgetter`: `lambda a, b: getattr(a, b)` 125 | 126 | `functools` 包中提供了一些高阶函数用于函数式编程,如:`reduce` 和 `partial`。 127 | 此外,`functools.wraps` 可以保留函数的一些元信息,在编写装饰器时经常会用到。 128 | 129 | 130 | 131 | ```python 132 | # Bonus: 获取闭包中的内容 133 | def fib_generator(): 134 | i, j = 0, 1 135 | def f(): 136 | nonlocal i, j 137 | i, j = j, i + j 138 | return i 139 | return f 140 | 141 | c = fib_generator() 142 | for _ in range(5): 143 | print(c(), end=' ') 144 | print() 145 | print(dict(zip( 146 | c.__code__.co_freevars, 147 | (x.cell_contents for x in c.__closure__)))) 148 | ``` 149 | 150 | 1 1 2 3 5 151 | {'i': 5, 'j': 8} 152 | 153 | -------------------------------------------------------------------------------- /markdown/第06章:使用一等函数实现设计模式.md: -------------------------------------------------------------------------------- 1 | 2 | # 使用一等函数实现设计模式 3 | 4 | > 符合模式并不表示做得对。 5 | > ——Ralph Johnson: 经典的《设计模式:可复用面向对象软件的基础》的作者之一 6 | 7 | 本章将对现有的一些设计模式进行简化,从而减少样板代码。 8 | 9 | ## 策略模式 10 | 实现策略模式,可以依赖 `abc.ABC` 和 `abc.abstractmethod` 来构建抽象基类。 11 | 但为了实现“策略”,并不一定要针对每种策略来编写子类,如果需求简单,编写函数就好了。 12 | 我们可以通过 `globals()` 函数来发现所有策略函数,并遍历并应用策略函数来找出最优策略。 13 | 14 | ## 命令模式 15 | “命令模式”的目的是解耦操作调用的对象(调用者)和提供实现的对象(接受者)。 16 | 17 | 在 Python 中,我们可以通过定义 `Command` 基类来规范命令调用协议;通过在类上定义 `__call__` 函数,还可以使对象支持直接调用。 18 | 19 | ```python 20 | import abc 21 | 22 | class BaseCommand(ABC): 23 | def execute(self, *args, **kwargs): 24 | raise NotImplemented 25 | ``` 26 | 27 | > 事实证明,在 Gamma 等人合著的那本书中,尽管大部分使用 C++ 代码说明(少数使用 Smalltalk),但是 23 个“经典的”设计模式都能很好地在“经典的”Java 中运用。然而,这并不意味着所有模式都能一成不变地在任何语言中运用。 28 | -------------------------------------------------------------------------------- /markdown/第07章:函数装饰器与闭包.md: -------------------------------------------------------------------------------- 1 | 2 | # 函数装饰器与闭包 3 | 4 | > 有很多人抱怨,把这个特性命名为“装饰器”不好。主要原因是,这个名称与 GoF 书使用的不一致。**装饰器**这个名称可能更适合在编译器领域使用,因为它会遍历并注解语法书。 5 | > —“PEP 318 — Decorators for Functions and Methods” 6 | 7 | 本章的最终目标是解释清楚函数装饰器的工作原理,包括最简单的注册装饰器和较复杂的参数化装饰器。 8 | 9 | 讨论内容: 10 | * Python 如何计算装饰器语法 11 | * Python 如何判断变量是不是局部的 12 | * 闭包存在的原因和工作原理 13 | * `nonlocal` 能解决什么问题 14 | * 实现行为良好的装饰器 15 | * 标准库中有用的装饰器 16 | * 实现一个参数化的装饰器 17 | 18 | 装饰器是可调用的对象,其参数是一个函数(被装饰的函数)。装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。 19 | 20 | 装饰器两大特性: 21 | 1. 能把被装饰的函数替换成其他函数 22 | 2. 装饰器在加载模块时立即执行 23 | 24 | 25 | ```python 26 | # 装饰器通常会把函数替换成另一个函数 27 | def decorate(func): 28 | def wrapped(): 29 | print('Running wrapped()') 30 | return wrapped 31 | 32 | @decorate 33 | def target(): 34 | print('running target()') 35 | 36 | target() 37 | # 以上写法等同于 38 | def target(): 39 | print('running target()') 40 | 41 | target = decorate(target) 42 | target() 43 | ``` 44 | 45 | 46 | ```python 47 | # 装饰器在导入时(模块加载时)立即执行 48 | registry = [] 49 | def register(func): 50 | print('running register {}'.format(func)) 51 | registry.append(func) 52 | return func 53 | 54 | @register 55 | def f1(): 56 | print('running f1()') 57 | 58 | @register 59 | def f2(): 60 | print('running f2()') 61 | 62 | 63 | print('registry →', registry) 64 | ``` 65 | 66 | 上面的装饰器会原封不动地返回被装饰的函数,而不一定会对函数做修改。 67 | 这种装饰器叫注册装饰器,通过使用它来中心化地注册函数,例如把 URL 模式映射到生成 HTTP 响应的函数上的注册处。 68 | 69 | ```python 70 | @app.get('/') 71 | def index(): 72 | return "Welcome." 73 | ``` 74 | 75 | 可以使用装饰器来实现策略模式,通过它来注册并获取所有的策略。 76 | 77 | 78 | ```python 79 | # 变量作用域规则 80 | b = 1 81 | def f2(a): 82 | print(a) 83 | print(b) # 因为 b 在后面有赋值操作,所以认为 b 为局部变量,所以referenced before assignment 84 | b = 2 85 | 86 | f2(3) 87 | ``` 88 | 89 | 90 | ```python 91 | # 使用 global 声明 b 为全局变量 92 | b = 1 93 | def f3(a): 94 | global b 95 | print(a) 96 | print(b) 97 | b = 9 98 | 99 | print(b) 100 | f3(2) 101 | print(b) 102 | ``` 103 | 104 | 105 | ```python 106 | # 闭包 107 | # 涉及嵌套函数时,才会产生闭包问题 108 | def register(): 109 | rrrr = [] # 叫 registry 会跟上面的变量重名掉… 110 | def wrapped(n): 111 | print(locals()) # locals() 的作用域延伸到了 wrapped 之外 112 | rrrr.append(n) 113 | return rrrr 114 | return wrapped 115 | 116 | # num 为**自由变量**,它未在本地作用域中绑定,但函数可以在其本身的作用域之外引用这个变量 117 | c = register() 118 | print(c(1)) 119 | print(c(2)) 120 | assert 'rrrr' not in locals() 121 | 122 | # 获取函数中的自由变量 123 | print({ 124 | name: cell.cell_contents 125 | for name, cell in zip(c.__code__.co_freevars, c.__closure__) 126 | }) 127 | ``` 128 | 129 | {'n': 1, 'rrrr': []} 130 | [1] 131 | {'n': 2, 'rrrr': [1]} 132 | [1, 2] 133 | {'rrrr': [1, 2]} 134 | 135 | 136 | 137 | ```python 138 | # 闭包内变量赋值与 nonlocal 声明 139 | def counter(): 140 | n = 0 141 | def count(): 142 | n += 1 # n = n + 1, 所以将 n 视为局部变量,但未声明,触发 UnboundLocalError 143 | return n 144 | return count 145 | 146 | def counter(): 147 | n = 0 148 | def count(): 149 | nonlocal n # 使用 nonlocal 对 n 进行声明,它可以把 n 标记为局部变量 150 | n += 1 # 这个 n 和上面的 n 引用的时同一个值,更新这个,上面也会更新 151 | return n 152 | return count 153 | 154 | 155 | c = counter() 156 | print(c(), c()) 157 | ``` 158 | 159 | 1 2 160 | 161 | 162 | 163 | ```python 164 | # 开始实现装饰器 165 | # 装饰器的典型行为:把被装饰的函数替换成新函数,二者接受相同的参数,而且(通常)返回被装饰的函数本该返回的值,同时还会做些额外操作 166 | import time 167 | from functools import wraps 168 | 169 | def clock(func): 170 | @wraps(func) # 用 func 的部分标注属性(如 __doc__, __name__)覆盖新函数的值 171 | def clocked(*args, **kwargs): 172 | t0 = time.perf_counter() 173 | result = func(*args, **kwargs) 174 | t1 = time.perf_counter() 175 | print(t1 - t0) 176 | return result 177 | return clocked 178 | 179 | @clock 180 | def snooze(seconds): 181 | time.sleep(seconds) 182 | 183 | snooze(1) 184 | ``` 185 | 186 | 1.002901900000012 187 | 188 | 189 | Python 内置的三个装饰器分别为 `property`, `classmethod` 和 `staticmethod`. 190 | 但 Python 内置的库中,有两个装饰器很常用,分别为 `functools.lru_cache` 和 [`functools.singledispatch`](https://docs.python.org/3/library/functools.html#functools.singledispatch). 191 | 192 | 193 | ```python 194 | # lru_cache 195 | # 通过内置的 LRU 缓存来存储函数返回值 196 | # 使用它可以对部分递归函数进行优化(比如递归的阶乘函数)(不过也没什么人会这么写吧) 197 | from functools import lru_cache 198 | 199 | @lru_cache() 200 | def func(n): 201 | print(n, 'called') 202 | return n 203 | 204 | print(func(1)) 205 | print(func(1)) 206 | print(func(2)) 207 | ``` 208 | 209 | 1 called 210 | 1 211 | 1 212 | 2 called 213 | 2 214 | 215 | 216 | 217 | ```python 218 | # singledispatch 219 | # 单分派泛函数:将多个函数绑定在一起组成一个泛函数,它可以通过参数类型将调用分派至其他函数上 220 | from functools import singledispatch 221 | import numbers 222 | 223 | @singledispatch 224 | def func(obj): 225 | print('Object', obj) 226 | 227 | # 只要可能,注册的专门函数应该处理抽象基类,不要处理具体实现(如 int) 228 | @func.register(numbers.Integral) 229 | def _(n): 230 | print('Integer', n) 231 | 232 | # 可以使用函数标注来进行分派注册 233 | @func.register 234 | def _(s:str): 235 | print('String', s) 236 | 237 | func(1) 238 | func('test') 239 | func([]) 240 | ``` 241 | 242 | Integer 1 243 | Object test 244 | Object [] 245 | 246 | 247 | ### 叠放装饰器 248 | ```python 249 | @d1 250 | @d2 251 | def func(): 252 | pass 253 | 254 | # 等同于 255 | func = d1(d2(func)) 256 | ``` 257 | 258 | ### 参数化装饰器 259 | 为了方便理解,可以把参数化装饰器看成一个函数:这个函数接受任意参数,返回一个装饰器(参数为 func 的另一个函数)。 260 | 261 | 262 | ```python 263 | # 参数化装饰器 264 | def counter(start=1): 265 | def decorator(func): 266 | n = start 267 | def wrapped(*args, **kwargs): 268 | nonlocal n 269 | print(f'{func.__name__} called {n} times.') 270 | n += 1 271 | return func(*args, **kwargs) 272 | return wrapped 273 | return decorator 274 | 275 | def test(): 276 | return 277 | 278 | t1 = counter(start=1)(test) 279 | t1() 280 | t1() 281 | 282 | @counter(start=2) 283 | def t2(): 284 | return 285 | 286 | t2() 287 | t2() 288 | ``` 289 | 290 | test called 1 times. 291 | test called 2 times. 292 | t2 called 2 times. 293 | t2 called 3 times. 294 | 295 | 296 | 297 | ```python 298 | # (可能是)更简洁的装饰器实现方式 299 | # 利用 class.__call__ 300 | 301 | class counter: 302 | def __init__(self, func): 303 | self.n = 1 304 | self.func = func 305 | 306 | def __call__(self, *args, **kwargs): 307 | print(f'{self.func.__name__} called {self.n} times.') 308 | self.n += 1 309 | return self.func(*args, **kwargs) 310 | 311 | @counter 312 | def t3(): 313 | return 314 | 315 | t3() 316 | t3() 317 | ``` 318 | 319 | t3 called 1 times. 320 | t3 called 2 times. 321 | 322 | 323 | 推荐阅读:[decorator 第三方库](http://decorator.readthedocs.io/en/latest/) 324 | 325 | 326 | ```python 327 | from decorator import decorator 328 | 329 | @decorator 330 | def counter(func, *args, **kwargs): 331 | if not hasattr(func, 'n'): 332 | func.n = 1 333 | print(f'{func.__qualname__} called {func.n} times.') 334 | retval = func(*args, **kwargs) 335 | func.n += 1 336 | return retval 337 | 338 | 339 | @counter 340 | def f(n): 341 | return n 342 | 343 | print(f(2)) 344 | print(f(3)) 345 | ``` 346 | 347 | f called 1 times. 348 | 2 349 | f called 2 times. 350 | 3 351 | 352 | -------------------------------------------------------------------------------- /markdown/第08章:对象引用、可变性和垃圾回收.md: -------------------------------------------------------------------------------- 1 | 2 | # 对象引用、可变性和垃圾回收 3 | 4 | ~~这章的前言实在是太长了…~~ 5 | 6 | Python 使用引用式变量:变量和变量名是两个不同的东西。 7 | 8 | 在 Python 中,变量不是一个存储数据的盒子,而是一个针对盒子的标注。同时,一个盒子上可以有很多标注,也可以一个都没有。 9 | 10 | 11 | ```python 12 | a = [1, 2, 3] 13 | b = a 14 | a.append(4) 15 | print(b) 16 | 17 | # 对象会在赋值之前创建 18 | class Test: 19 | def __init__(self): 20 | print('ID', id(self)) # 在把 self 分配给 t 之前,self 就已经有了自己的 ID 21 | 22 | t = Test() 23 | print('Test', t, id(t)) 24 | ``` 25 | 26 | [1, 2, 3, 4] 27 | ID 88908560 28 | Test <__main__.Test object at 0x054CA310> 88908560 29 | 30 | 31 | 32 | ```python 33 | # 分清“==”和“is” 34 | # a is b 等同于 id(a) == id(b) 35 | a = {'a': 1} 36 | b = a 37 | assert a is b 38 | assert id(a) == id(b) 39 | 40 | c = {'a': 1} 41 | assert a == c 42 | assert not a is c 43 | assert id(a) != id(c) 44 | ``` 45 | 46 | 在 CPython 中,id() 返回对象的内存地址,但在其他 Python 解释器中可能是别的值。关键是,ID 一定是唯一的数值标注,而且在对象的生命周期中绝不会变。 47 | 编程中,我们很少会使用 `id()` 函数,标识最常使用 `is` 运算符检查相同性,而不是直接比较 ID. 48 | 49 | 50 | ```python 51 | # 与单例进行比较时,应该使用 is 52 | a = None 53 | assert a is None 54 | b = ... 55 | assert b is ... 56 | 57 | # a == b 是语法糖,等同于 a.__eq__(b) 58 | ``` 59 | 60 | 61 | ```python 62 | # 元组的相对不可变性 63 | t1 = (1, 2, [30, 40]) 64 | t2 = (1, 2, [30, 40]) 65 | assert t1 == t2 66 | 67 | id1 = id(t1[-1]) 68 | t1[-1].append(50) 69 | print(t1) 70 | 71 | id2 = id(t1[-1]) 72 | assert id1 == id2 # t1 的最后一个元素的标识没变,但是值变了 73 | assert t1 != t2 74 | ``` 75 | 76 | (1, 2, [30, 40, 50]) 77 | 78 | 79 | 80 | ```python 81 | # 浅复制 82 | # Python 对列表等进行复制时,只会复制容器,而不会复制容器内的内容 83 | a = [1, [2, 3], (4, 5, 6)] 84 | b = list(a) 85 | assert a == b 86 | assert a is not b # b 是一个新对象 87 | assert a[1] is b[1] # 但两个列表中的元素是同一个 88 | 89 | a[1] += [4, 5] # a[1] = a[1] + [4, 5], list 就地执行操作后返回对象本身的引用 90 | b[2] += (7, 8) # b[2] = b[2] + (7, 8), tuple 在执行操作后生成一个新对象并返回它的引用 91 | print(a, b) 92 | ``` 93 | 94 | [1, [2, 3, 4, 5], (4, 5, 6)] [1, [2, 3, 4, 5], (4, 5, 6, 7, 8)] 95 | 96 | 97 | 98 | ```python 99 | # 深复制 100 | from copy import deepcopy 101 | a = [1, [2, 3], (4, 5, 6)] 102 | b = deepcopy(a) 103 | assert a == b 104 | assert a[1] is not b[1] # 不单复制了容器,还复制了容器中的值 105 | 106 | b[1].extend([4, 5]) 107 | print(a, b) 108 | assert a != b 109 | ``` 110 | 111 | [1, [2, 3], (4, 5, 6)] [1, [2, 3, 4, 5], (4, 5, 6)] 112 | 113 | 114 | 115 | ```python 116 | # Python 的传参方式:共享传参(call by sharing),只会传递引用 117 | def f(a, b): 118 | a += b 119 | return a 120 | 121 | a, b = [1, 2], [3, 4] 122 | c = f(a, b) 123 | assert a == c == [1, 2, 3, 4] 124 | assert a is c 125 | ``` 126 | 127 | 128 | ```python 129 | # 共享传参导致的问题 130 | # 可变参数作为函数默认值 & 跨作用域引用导致共享变量更改 131 | class Container: 132 | def __init__(self, initial=[]): 133 | self.values = initial 134 | 135 | def add(self, value): 136 | self.values.append(value) 137 | 138 | a = Container() 139 | b = Container() 140 | l = [] 141 | c = Container(l) 142 | a.add(1) 143 | b.add(2) 144 | c.add(3) 145 | print(a.values, b.values, c.values) 146 | assert a.values is b.values # a.values 和 b.values 共享同一个变量(init 函数中的默认值) 147 | print(l) # l 和 c.values 共享同一个变量,c.values 更改后,l 也会更改 148 | assert c.values is l 149 | ``` 150 | 151 | [1, 2] [1, 2] [3] 152 | [3] 153 | 154 | 155 | ## del 和垃圾回收 156 | > 对象绝不会自行销毁;然而,无法得到对象时,可能会被当做垃圾回收。 157 | 158 | 在 CPython 中,垃圾回收使用的主要算法时引用计数。当引用计数归零时,对象立即就被销毁。 159 | 销毁时,CPython 会先调用对象的 `__del__` 方法,然后释放分配给对象的内存。 160 | CPython 2.0 增加了分代垃圾回收机制,用于检测循环引用中涉及的对象组。 161 | 162 | 163 | ```python 164 | # 监测引用计数垃圾回收 165 | import weakref 166 | s1 = s2 = {1} 167 | ender = weakref.finalize(s1, lambda: print('Bye')) 168 | print(ender.alive) 169 | del s1 # 删除 s1 的引用 170 | print(ender.alive) # 对象 {1} 的引用还在(s2) 171 | s2 = {2} 172 | print(ender.alive) # 无法引用到 {1} 对象,于是它被垃圾回收 173 | ``` 174 | 175 | True 176 | True 177 | Bye 178 | False 179 | 180 | 181 | ## 弱引用 182 | 使用 `weakref.ref` 可以生成一个对象的引用,但不会增加它的引用计数 183 | 184 | `weakref.ref` 类实际上是低层接口,多数程序最好使用 `weakref` 集合和 `finalize`. 185 | 186 | `weakref` 提供的弱引用集合有 `WeakKeyDictionary`, `WeakValueDictionary`, `WeakSet`. 187 | 它们的用途可以从名字中得出。 188 | 189 | 弱引用的局限:并不是所有对象都可以创建弱引用,比如 `list`, `dict`, `str` 实例。 190 | 但是,某些自定义类都可以创建弱引用(比如基于 `list` 生成的子类)。 191 | `int` 和 `tuple` 实例及它们的子类实例都不能成为弱引用的目标。 192 | 193 | 194 | ```python 195 | import weakref 196 | 197 | s1 = {1} 198 | ref = weakref.ref(s1) 199 | print(ref, ref()) 200 | del s1 201 | print(ref, ref()) 202 | ``` 203 | 204 | {1} 205 | None 206 | 207 | 208 | 209 | ```python 210 | # WeakValueDictionary 实例 211 | from weakref import WeakValueDictionary 212 | 213 | weak_dict = WeakValueDictionary() 214 | s1, s2 = {1}, {2} 215 | weak_dict.update({ 216 | 's1': s1, 217 | 's2': s2 218 | }) 219 | print(list(weak_dict.items())) 220 | del s2 221 | print(list(weak_dict.items())) 222 | ``` 223 | 224 | [('s1', {1}), ('s2', {2})] 225 | [('s1', {1})] 226 | 227 | 228 | ## 一些杂事 229 | 如果不可变集合(如 `tuple` 和 `frozenset`)中保存的是可变元素的引用,那么可变元素的值发生变化后,不可变集合也会发生改变。 230 | 这里指的是 `hash` 和 `==`的结果,即使集合中的对象更改,该对象在集合中存储的引用也是不会变的。 231 | 232 | `tuple()` 的参数如果是一个元组,则得到的是同一个对象。对元组使用 `[:]` 切片操作也不会生成新对象。 233 | `str`, `bytes` 和 `frozenset` 实例也有这种行为。 234 | `fs.copy()` 返回的是它本身(喵喵喵?)。 235 | 236 | 字符串字面量可能会产生**驻留**现象:两个相等的字符串共享同一个字符串对象。 237 | `int` 对象中,在 [-5, 256] 之间的整型实例也被提前创建,所有指向这些数字的引用均会共享对象。 238 | 239 | 自定义对象中,如果没有实现 `__eq__` 方法,则进行 `==` 判断时会比较它们的 ID. 240 | 这种后备机制认为用户定义的类的各个实例是不同的。 241 | 242 | 243 | ```python 244 | a = "hahaha" 245 | b = "hahaha" 246 | assert a is b 247 | 248 | a = 66 249 | b = 66 250 | assert a is b 251 | a = 257 252 | b = 257 253 | assert a is not b 254 | 255 | class T: 256 | pass 257 | 258 | a = T() 259 | b = a 260 | c = T() 261 | assert a is b 262 | assert a is not c 263 | ``` 264 | 265 | ## 延伸阅读 266 | [Python Garbage Collector Implementations: CPython, PyPy and Gas](https://thp.io/2012/python-gc/python_gc_final_2012-01-22.pdf) 267 | -------------------------------------------------------------------------------- /markdown/第09章:符合Python风格的对象.md: -------------------------------------------------------------------------------- 1 | 2 | # 符合 Python 风格的对象 3 | > 绝对不要使用两个前导下划线,这是很烦人的自私行为。 4 | > ——Ian Bicking 5 | > pip, virtualenv 和 Paste 等项目的创建者 6 | 7 | 得益于 Python 数据模型,自定义类型的行为可以像内置类型那样自然。 8 | 实现如此自然的行为,靠的不是继承,而是**鸭子类型**(duck typing):我们只需按照预定行为实现对象所需的方法即可。 9 | 10 | 当然,这些方法不是必须要实现的,我们需要哪些功能,再去实现它就好。 11 | 12 | 书中的 `Vector` 类完整实现可见[官方 Repo](https://github.com/fluentpython/example-code/blob/master/09-pythonic-obj/vector2d_v3.py). 13 | 14 | ## 对象表现形式 15 | Python 中,有两种获取对象的字符串表示形式的标准方式: 16 | * `repr()`:以便于开发者理解的方式返回对象的字符串表示形式 17 | * `str()`:以便于用户理解的方式返回对象的字符串表示形式 18 | 19 | 实现 `__repr__` 和 `__str__` 两个特殊方法,可以分别为 `repr` 和 `str` 提供支持。 20 | 21 | ## classmethod & staticmethod 22 | `classmethod`: 定义操作**类**,而不是操作**实例**的方法。 23 | `classmethod` 最常见的用途是定义备选构造方法,比如 `datetime.fromordinal` 和 `datetime.fromtimestamp`. 24 | 25 | `staticmethod` 也会改变方法的调用方式,但方法的第一个参数不是特殊的值(`self` 或 `cls`)。 26 | `staticmethod` 可以把一些静态函数定义在类中而不是模块中,但抛开 `staticmethod`,我们也可以用其它方法来实现相同的功能。 27 | 28 | 29 | ```python 30 | class Demo: 31 | def omethod(*args): 32 | # 第一个参数是 Demo 对象 33 | return args 34 | @classmethod 35 | def cmethod(*args): 36 | # 第一个参数是 Demo 类 37 | return args 38 | @staticmethod 39 | def smethod(*args): 40 | # 第一个参数不是固定的,由调用者传入 41 | return args 42 | 43 | print(Demo.cmethod(1), Demo.smethod(1)) 44 | demo = Demo() 45 | print(demo.cmethod(1), demo.smethod(1), demo.omethod(1)) 46 | ``` 47 | 48 | (, 1) (1,) 49 | (, 1) (1,) (<__main__.Demo object at 0x05E719B0>, 1) 50 | 51 | 52 | ## 字符串模板化 53 | `__format__` 实现了 `format` 方法的接口,它的参数为 `format` 格式声明符,格式声明符的表示法叫做[格式规范微语言](https://docs.python.org/3/library/string.html#formatspec)。 54 | `str.format` 的声明符表示法和格式规范微语言类似,称作[格式字符串句法](https://docs.python.org/3/library/string.html#formatstrings)。 55 | 56 | 57 | ```python 58 | # format 59 | num = 15 60 | print(format(num, '2d'), format(num, '.2f'), format(num, 'X')) 61 | ``` 62 | 63 | 15 15.00 F 64 | 65 | 66 | ## 对象散列化 67 | 只有可散列的对象,才可以作为 `dict` 的 key,或者被加入 `set`. 68 | 要想创建可散列的类型,只需要实现 `__hash__` 和 `__eq__` 即可。 69 | 有一个要求:如果 `a == b`,那么 `hash(a) == hash(b)`. 70 | 71 | 文中推荐的 hash 实现方法是使用位运算异或各个分量的散列值: 72 | ```python 73 | class Vector2d: 74 | # 其余部分省略 75 | def __hash__(self): 76 | return hash(self.x) ^ hash(self.y) 77 | ``` 78 | 79 | 而最新的文章中,推荐把各个分量组成一个 `tuple`,然后对其进行散列: 80 | ```python 81 | class Vector2d: 82 | def __hash__(self): 83 | return hash((self.x, self.y)) 84 | ``` 85 | 86 | ## Python 的私有属性和“受保护的”属性 87 | Python 的“双下前导”变量:如果以 `__a` 的形式(双前导下划线,尾部最多有一个下划线)命名一个实例属性,Python 会在 `__dict__` 中将该属性进行“名称改写”为 `_Klass__a`,以防止外部对对象内部属性的直接访问。 88 | 这样可以避免意外访问,但**不能防止故意犯错**。 89 | 90 | 有一种观点认为不应该使用“名称改写”特性,对于私有方法来说,可以使用前导单下划线 `_x` 来标注,而不应使用双下划线。 91 | 92 | 此外:在 `from mymod import *` 中,任何使用下划线前缀(无论单双)的变量,都不会被导入。 93 | 94 | 95 | ```python 96 | class Vector2d: 97 | def __init__(self, x, y): 98 | self.__x = x 99 | self.__y = y 100 | 101 | vector = Vector2d(1, 2) 102 | print(vector.__dict__) 103 | print(vector._Vector2d__x) 104 | ``` 105 | 106 | {'_Vector2d__x': 1, '_Vector2d__y': 2} 107 | 1 108 | 109 | 110 | ## 使用 __slot__ 类属性节省空间 111 | 默认情况下,Python 在各个实例的 `__dict__` 字典中存储实例属性。但 Python 字典使用散列表实现,会消耗大量内存。 112 | 如果需要处理很多个属性**有限且相同**的实例,可以通过定义 `__slots__` 类属性,将实例用元组存储,以节省内存。 113 | 114 | ```python 115 | class Vector2d: 116 | __slots__ = ('__x', '__y') 117 | ``` 118 | 119 | `__slots__` 的目的是优化内存占用,而不是防止别人在实例中添加属性。_所以一般也没有什么使用的必要。_ 120 | 121 | 使用 `__slots__` 时需要注意的地方: 122 | * 子类不会继承父类的 `__slots_` 123 | * 实例只能拥有 `__slots__` 中所列出的属性 124 | * 如果不把 `"__weakref__"` 加入 `__slots__`,实例就不能作为弱引用的目标。 125 | 126 | 127 | ```python 128 | # 对象二进制化 129 | from array import array 130 | import math 131 | 132 | class Vector2d: 133 | typecode = 'd' 134 | 135 | def __init__(self, x, y): 136 | self.x = x 137 | self.y = y 138 | 139 | def __str__(self): 140 | return str(tuple(self)) 141 | 142 | def __iter__(self): 143 | yield from (self.x, self.y) 144 | 145 | def __bytes__(self): 146 | return (bytes([ord(self.typecode)]) + 147 | bytes(array(self.typecode, self))) 148 | 149 | def __eq__(self, other): 150 | return tuple(self) == tuple(other) 151 | 152 | @classmethod 153 | def frombytes(cls, octets): 154 | typecode = chr(octets[0]) 155 | memv = memoryview(octets[1:]).cast(typecode) 156 | return cls(*memv) 157 | 158 | vector = Vector2d(1, 2) 159 | v_bytes = bytes(vector) 160 | vector2 = Vector2d.frombytes(v_bytes) 161 | print(v_bytes) 162 | print(vector, vector2) 163 | assert vector == vector2 164 | ``` 165 | 166 | b'd\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00@' 167 | (1, 2) (1.0, 2.0) 168 | 169 | -------------------------------------------------------------------------------- /markdown/第10章:序列的修改、散列和切片.md: -------------------------------------------------------------------------------- 1 | 2 | # 序列的修改、散列和切片 3 | > 不要检查它**是不是**鸭子、它的**叫声**像不像鸭子、它的**走路姿势**像不像鸭子,等等。具体检查什么取决于你想用语言的哪些行为。(comp.lang.python, 2000 年 7 月 26 日) 4 | > ——Alex Martelli 5 | 6 | 本章在 `Vector2d` 基础上进行改进以支持多维向量。不过我不想写那么多 `Vector` 代码了,所以我会尝试对里面讲到的知识进行一些抽象。 7 | 8 | 当然,如果有兴趣也可以研究一下书中实现的[多维向量代码](https://github.com/fluentpython/example-code/tree/master/10-seq-hacking)。 9 | 10 | ## Python 序列协议 11 | 鸭子类型(duck typing):“如果一个东西长的像鸭子、叫声像鸭子、走路像鸭子,那我们可以认为它就是鸭子。” 12 | 13 | Python 中,如果我们在类上实现了 `__len__` 和 `__getitem__` 接口,我们就可以把它用在任何期待序列的场景中。 14 | 15 | 16 | ```python 17 | # 序列协议中的切片 18 | class SomeSeq: 19 | def __init__(self, seq): 20 | self._seq = list(seq) 21 | 22 | def __len__(self): 23 | return len(self._seq) 24 | 25 | def __getitem__(self, index): 26 | if isinstance(index, slice): 27 | # 专门对 slice 做一个自己的实现 28 | start, stop = index.start, index.stop 29 | step = index.step 30 | 31 | if start is None: 32 | start = 0 33 | elif start < 0: 34 | start = len(self) + start 35 | else: 36 | start = min(start, len(self)) 37 | if stop is None: 38 | stop = len(self) 39 | elif stop < 0: 40 | stop = len(self) + stop 41 | else: 42 | stop = min(stop, len(self)) 43 | if step is None: 44 | step = 1 45 | elif step == 0: 46 | raise ValueError("slice step cannot be zero") 47 | 48 | # 以上的复杂逻辑可以直接使用 slice 的接口 49 | # start, stop, step = index.indices(len(self)) 50 | index_range = range(start, stop, step) 51 | return [self._seq[i] for i in index_range] 52 | else: 53 | return self._seq[index] 54 | 55 | 56 | seq = SomeSeq([1, 2, 3, 4, 5]) 57 | print(seq[2]) 58 | print(seq[2:4], seq[:5], seq[:5:2], seq[3:], seq[:200]) 59 | print(seq[:-1], seq[-1:-5:-1]) 60 | ``` 61 | 62 | 3 63 | [3, 4] [1, 2, 3, 4, 5] [1, 3, 5] [4, 5] [1, 2, 3, 4, 5] 64 | [1, 2, 3, 4] [5, 4, 3, 2] 65 | 66 | 67 | 68 | ```python 69 | # __getitem__ 的参数不一定是单个值或者 slice,还有可能是 tuple 70 | class SomeSeq: 71 | def __init__(self, seq): 72 | self._seq = list(seq) 73 | 74 | def __len__(self): 75 | return len(self._seq) 76 | 77 | def __getitem__(self, item): 78 | return item 79 | 80 | seq = SomeSeq([1, 2, 3, 4, 5]) 81 | print(seq[1, 2, 3]) 82 | print(seq[:5, 2:5:2, -1:5:3]) 83 | ``` 84 | 85 | (1, 2, 3) 86 | (slice(None, 5, None), slice(2, 5, 2), slice(-1, 5, 3)) 87 | 88 | 89 | 90 | ```python 91 | # zip 和 enumerate: 知道这两个方法可以简化一些场景 92 | l1, l2 = [1, 2, 3], [1, 2, 3, 4, 5] 93 | for n1, n2 in zip(l1, l2): 94 | print(n1, n2) 95 | 96 | print('---') 97 | # 不要这么写 98 | # for index in range(len(l1)): 99 | # print(index, l1[index]) 100 | for index, obj in enumerate(l1): 101 | print(index, obj) 102 | 103 | print('---') 104 | # list 分组的快捷操作 105 | # 注意:只用于序列长度是分组的倍数的场景,否则最后一组会丢失 106 | l = [1,2,3,4,5,6,7,8,9] 107 | print(list(zip(*[iter(l)] * 3))) 108 | ``` 109 | 110 | 1 1 111 | 2 2 112 | 3 3 113 | --- 114 | 0 1 115 | 1 2 116 | 2 3 117 | --- 118 | [(1, 2, 3), (4, 5, 6), (7, 8, 9)] 119 | 120 | -------------------------------------------------------------------------------- /markdown/第11章:接口:从协议到抽象基类.md: -------------------------------------------------------------------------------- 1 | 2 | # 接口:从协议到抽象基类 3 | > 抽象类表示接口。 4 | > ——Bjarne Stroustrup, C++ 之父 5 | 6 | 本章讨论的话题是接口:从**鸭子类型**的代表特征动态协议,到使接口更明确、能验证实现是否符合规定的抽象基类(Abstract Base Class, ABC)。 7 | 8 | > 接口的定义:对象公开方法的子集,让对象在系统中扮演特定的角色。 9 | > 协议是接口,但不是正式的(只由文档和约定定义),因此协议不能像正式接口那样施加限制。 10 | > 允许一个类上只实现部分接口。 11 | 12 | ## 抽象基类(abc) 13 | 抽象基类是一个非常实用的功能,可以使用抽象基类来检测某个类是否实现了某种协议,而这个类并不需要继承自这个抽象类。 14 | [`collections.abc`](https://docs.python.org/3/library/collections.abc.html) 和 [`numbers`](https://docs.python.org/3/library/numbers.html) 模块中提供了许多常用的抽象基类以用于这种检测。 15 | 16 | 有了这个功能,我们在自己实现函数时,就不需要非常关心外面传进来的参数的**具体类型**(`isinstance(param, list)`),只需要关注这个参数是否支持我们**需要的协议**(`isinstance(param, abc.Sequence`)以保障正常使用就可以了。 17 | 18 | 但是注意:从 Python 简洁性考虑,最好不要自己创建新的抽象基类,而应尽量考虑使用现有的抽象基类。 19 | 20 | 21 | ```python 22 | # 抽象基类 23 | from collections import abc 24 | 25 | 26 | class A: 27 | pass 28 | 29 | class B: 30 | def __len__(self): 31 | return 0 32 | 33 | assert not isinstance(A(), abc.Sized) 34 | assert isinstance(B(), abc.Sized) 35 | assert abc.Sequence not in list.__bases__ # list 并不是 Sequence 的子类 36 | assert isinstance([], abc.Sequence) # 但是 list 实例支持序列协议 37 | ``` 38 | 39 | 40 | ```python 41 | # 在抽象基类上进行自己的实现 42 | from collections import abc 43 | 44 | class FailedSized(abc.Sized): 45 | pass 46 | 47 | 48 | class NormalSized(abc.Sized): 49 | def __len__(self): 50 | return 0 51 | 52 | 53 | n = NormalSized() 54 | print(len(n)) 55 | f = FailedSized() # 基类的抽象协议未实现,Python 会阻止对象实例化 56 | ``` 57 | 58 | 0 59 | 60 | 61 | 62 | --------------------------------------------------------------------------- 63 | 64 | TypeError Traceback (most recent call last) 65 | 66 | in () 67 | 13 n = NormalSized() 68 | 14 print(len(n)) 69 | ---> 15 f = FailedSized() # 协议未实现,Python 会阻止对象实例化 70 | 71 | 72 | TypeError: Can't instantiate abstract class FailedSized with abstract methods __len__ 73 | 74 | 75 | 有一点需要注意:抽象基类上的方法并不都是抽象方法。 76 | 换句话说,想继承自抽象基类,只需要实现它上面**所有的抽象方法**即可,有些方法的实现是可选的。 77 | 比如 [`Sequence.__contains__`](https://github.com/python/cpython/blob/3.7/Lib/_collections_abc.py#L889),Python 对此有自己的实现(使用 `__iter__` 遍历自身,查找是否有相等的元素)。但如果你在 `Sequence` 之上实现的序列是有序的,则可以使用二分查找来覆盖 `__contains__` 方法,从而提高查找效率。 78 | 79 | 我们可以使用 `__abstractmethods__` 属性来查看某个抽象基类上的抽象方法。这个抽象基类的子类必须实现这些方法,才可以被正常实例化。 80 | 81 | 82 | ```python 83 | # 自己定义一个抽象基类 84 | import abc 85 | 86 | # 使用元类的定义方式是 class SomeABC(metaclass=abc.ABCMeta) 87 | class SomeABC(abc.ABC): 88 | @abc.abstractmethod 89 | def some_method(self): 90 | raise NotImplementedError 91 | 92 | 93 | class IllegalClass(SomeABC): 94 | pass 95 | 96 | class LegalClass(SomeABC): 97 | def some_method(self): 98 | print('Legal class OK') 99 | 100 | 101 | l = LegalClass() 102 | l.some_method() 103 | il = IllegalClass() # Raises 104 | ``` 105 | 106 | Legal class OK 107 | 108 | 109 | 110 | --------------------------------------------------------------------------- 111 | 112 | TypeError Traceback (most recent call last) 113 | 114 | in () 115 | 19 l = LegalClass() 116 | 20 l.some_method() 117 | ---> 21 il = IllegalClass() # Raises 118 | 119 | 120 | TypeError: Can't instantiate abstract class IllegalClass with abstract methods some_method 121 | 122 | 123 | ## 虚拟子类 124 | 使用 [`register`](https://docs.python.org/3/library/abc.html#abc.ABCMeta.register) 接口可以将某个类注册为某个 ABC 的“虚拟子类”。支持 `register` 直接调用注册,以及使用 `@register` 装饰器方式注册(其实这俩是一回事)。 125 | 注册后,使用 `isinstance` 以及实例化时,解释器将不会对虚拟子类做任何方法检查。 126 | 注意:虚拟子类不是子类,所以虚拟子类不会继承抽象基类的任何方法。 127 | 128 | 129 | ```python 130 | # 虚拟子类 131 | import abc 132 | import traceback 133 | 134 | class SomeABC(abc.ABC): 135 | @abc.abstractmethod 136 | def some_method(self): 137 | raise NotImplementedError 138 | 139 | def another_method(self): 140 | print('Another') 141 | 142 | @classmethod 143 | def __subclasshook__(cls, subcls): 144 | """ 145 | 在 register 或者进行 isinstance 判断时进行子类检测 146 | https://docs.python.org/3/library/abc.html#abc.ABCMeta.__subclasshook__ 147 | """ 148 | print('Subclass:', subcls) 149 | return True 150 | 151 | 152 | class IllegalClass: 153 | pass 154 | 155 | SomeABC.register(IllegalClass) # 注册 156 | il = IllegalClass() 157 | assert isinstance(il, IllegalClass) 158 | assert SomeABC not in IllegalClass.__mro__ # isinstance 会返回 True,但 IllegalClass 并不是 SomeABC 的子类 159 | try: 160 | il.some_method() # 虚拟子类不是子类,不会从抽象基类上继承任何方法 161 | except Exception as e: 162 | traceback.print_exc() 163 | 164 | try: 165 | il.another_method() 166 | except Exception as e: 167 | traceback.print_exc() 168 | 169 | ``` 170 | 171 | Subclass: 172 | 173 | 174 | Traceback (most recent call last): 175 | File "", line 31, in 176 | il.some_method() # 虚拟子类不是子类,不会从抽象基类上继承任何方法 177 | AttributeError: 'IllegalClass' object has no attribute 'some_method' 178 | Traceback (most recent call last): 179 | File "", line 36, in 180 | il.another_method() 181 | AttributeError: 'IllegalClass' object has no attribute 'another_method' 182 | 183 | -------------------------------------------------------------------------------- /markdown/第12章:继承的优缺点.md: -------------------------------------------------------------------------------- 1 | 2 | # 继承的优缺点 3 | > (我们)推出继承的初衷是让新手顺利使用只有专家才能设计出来的框架。 4 | > ——Alan Klay, "The Early History of Smalltalk" 5 | 6 | 本章探讨继承和子类化,重点是说明对 Python 而言尤为重要的两个细节: 7 | * 子类化内置类型的缺点 8 | * 多重继承和方法解析顺序(MRO) 9 | 10 | ## 子类化类型的缺点 11 | 基本上,内置类型的方法不会调用子类覆盖的方法,所以尽可能不要去子类化内置类型。 12 | 如果有需要使用 `list`, `dict` 等类,`collections` 模块中提供了用于用户继承的 `UserDict`、`userList` 和 `UserString`,这些类经过特殊设计,因此易于扩展。 13 | 14 | 15 | ```python 16 | # 子类化内置类型的缺点 17 | class DoppelDict(dict): 18 | def __setitem__(self, key, value): 19 | super().__setitem__(key, [value] * 2) 20 | 21 | # 构造方法和 update 都不会调用子类的 __setitem__ 22 | dd = DoppelDict(one=1) 23 | dd['two'] = 2 24 | print(dd) 25 | dd.update(three=3) 26 | print(dd) 27 | 28 | 29 | from collections import UserDict 30 | class DoppelDict2(UserDict): 31 | def __setitem__(self, key, value): 32 | super().__setitem__(key, [value] * 2) 33 | 34 | # UserDict 中,__setitem__ 对 update 起了作用,但构造函数依然不会调用 __setitem__ 35 | dd = DoppelDict2(one=1) 36 | dd['two'] = 2 37 | print(dd) 38 | dd.update(three=3) 39 | print(dd) 40 | ``` 41 | 42 | {'one': 1, 'two': [2, 2]} 43 | {'one': 1, 'two': [2, 2], 'three': 3} 44 | {'one': [1, 1], 'two': [2, 2]} 45 | {'one': [1, 1], 'two': [2, 2], 'three': [3, 3]} 46 | 47 | 48 | ## 方法解析顺序(Method Resolution Order) 49 | 权威论述:[The Python 2.3 Method Resolution Order](https://www.python.org/download/releases/2.3/mro/) 50 | > Moreover, unless you make strong use of multiple inheritance and you have non-trivial hierarchies, you don't need to understand the C3 algorithm, and you can easily skip this paper. 51 | 52 | Emmm… 53 | 54 | OK,提两句: 55 | 1. 如果想查看某个类的方法解析顺序,可以访问该类的 `__mro__` 属性; 56 | 2. 如果想绕过 MRO 访问某个父类的方法,可以直接调用父类上的非绑定方法。 57 | 58 | 59 | ```python 60 | class A: 61 | def f(self): 62 | print('A') 63 | 64 | 65 | class B(A): 66 | def f(self): 67 | print('B') 68 | 69 | 70 | class C(A): 71 | def f(self): 72 | print('C') 73 | 74 | 75 | class D(B, C): 76 | def f(self): 77 | print('D') 78 | 79 | def b_f(self): 80 | "D -> B" 81 | super().f() 82 | 83 | def c_f(self): 84 | "B -> C" 85 | super(B, self).f() 86 | # C.f(self) 87 | 88 | def a_f(self): 89 | "C -> A" 90 | super(C, self).f() 91 | 92 | 93 | print(D.__mro__) 94 | d = D() 95 | d.f() 96 | d.b_f() 97 | d.c_f() 98 | d.a_f() 99 | ``` 100 | 101 | (, , , , ) 102 | D 103 | B 104 | C 105 | A 106 | 107 | 108 | ## 处理多重继承 109 | 书中列出来了一些处理多重继承的建议,以免做出令人费解和脆弱的继承设计: 110 | 1. 把接口继承和实现继承区分开 111 | 如果继承重用的是代码实现细节,通常可以换用组合和委托模式。 112 | 2. 使用抽象基类显式表示接口 113 | 如果基类的作用是定义接口,那就应该定义抽象基类。 114 | 3. 通过混入(Mixin)重用代码 115 | 如果一个类的作用是为多个不相关的子类提供方法实现,从而实现重用,但不体现实际的“上下级”关系,那就应该明确地将这个类定义为**混入类**(Mixin class)。关于 Mixin(我还是习惯英文名),可以看 Python3-Cookbook 的[《利用Mixins扩展类功能》](https://python3-cookbook.readthedocs.io/zh_CN/latest/c08/p18_extending_classes_with_mixins.html)章节。 116 | 4. 在名称中明确指出混入 117 | 在类名里使用 `XXMixin` 写明这个类是一个 Mixin. 118 | 5. 抽象基类可以作为混入,反过来则不成立 119 | 6. 不要子类化多个具体类 120 | 在设计子类时,不要在多个**具体基类**上实现多继承。一个子类最好只继承自一个具体基类,其余基类最好应为 Mixin,用于提供增强功能。 121 | 7. 为用户提供聚合类 122 | 如果抽象基类或 Mixin 的组合对客户代码非常有用,那就替客户实现一个包含多继承的聚合类,这样用户可以直接继承自你的聚合类,而不需要再引入 Mixin. 123 | 8. “优先使用对象组合,而不是类继承” 124 | 组合和委托可以代替混入,把行为提供给不同的类,不过这些方法不能取代接口继承去**定义类型层次结构**。 125 | 126 | 两个实际例子: 127 | * 很老的 `tkinter` 称为了反例,那个时候的人们还没有充分认识到多重继承的缺点; 128 | * 现代的 `Django` 很好地利用了继承和 Mixin。它提供了非常多的 `View` 类,鼓励用户去使用这些类以免除大量模板代码。 129 | -------------------------------------------------------------------------------- /markdown/第13章:正确重载运算符.md: -------------------------------------------------------------------------------- 1 | 2 | # 正确重载运算符 3 | > 有些事情让我不安,比如运算符重载。我决定不支持运算符重载,这完全是个个人选择,因为我见过太多 C++ 程序员滥用它。 4 | > ——James Gosling, Java 之父 5 | 6 | 本章讨论的内容是: 7 | * Python 如何处理中缀运算符(如 `+` 和 `|`)中不同类型的操作数 8 | * 使用鸭子类型或显式类型检查处理不同类型的操作数 9 | * 众多比较运算符(如 `==`、`>`、`<=` 等等)的特殊行为 10 | * 增量赋值运算符(如 `+=`)的默认处理方式和重载方式 11 | 12 | 重载运算符,如果使用得当,可以让代码更易于阅读和编写。 13 | Python 出于灵活性、可用性和安全性方面的平衡考虑,对运算符重载做了一些限制: 14 | * 不能重载内置类型的运算符 15 | * 不能新建运算符 16 | * 计算符 `is`、`and`、`or`、`not` 不能重载 17 | 18 | Python 算数运算符对应的魔术方法可以见[这里](https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types)。 19 | 20 | 一个小知识:二元运算符 `+` 和 `-` 对应的魔术方法是 `__add__` 和 `__sub__`,而一元运算符 `+` 和 `-` 对应的魔术方法是 `__pos__` 和 `__neg__`. 21 | 22 | ## 反向运算符 23 | > 为了支持涉及不同类型的运算,Python 为中缀运算符特殊方法提供了特殊的分派机制。对表达式 `a + b` 来说,解释器会执行以下几步操作。 24 | > 1. 如果 a 有 `__add__` 方法,而且返回值不是 `NotImplemented`,调用 `a.__add__`,然后返回结果。 25 | > 2. 如果 a 没有 `__add__` 方法,或者调用 `__add__` 方法返回 `NotImplemented`,检查 b 有没有 `__radd__` 方法,如果有,而且没有返回 `NotImplemented`,调用 `b.__radd__`,然后返回结果。 26 | > 3. 如果 b 没有 `__radd__` 方法,或者调用 `__radd__` 方法返回 `NotImplemented`,抛出 `TypeError`, 并在错误消息中指明操作数类型不支持。 27 | 28 | 这样一来,只要运算符两边的任何一个对象正确实现了运算方法,就可以正常实现二元运算操作。 29 | 30 | 小知识: 31 | * `NotImplemented` 是一个特殊的单例值,要 `return`;而 `NotImplementedError` 是一个异常,要 `raise`. 32 | * Python 3.5 新引入了 `@` 运算符,用于点积乘法,对应的魔术方法是 [`__matmul__`](https://docs.python.org/3/reference/datamodel.html#object.__matmul__). 33 | * 进行 `!=` 运算时,如果双方对象都没有实现 `__ne__`,解释器会尝试 `__eq__` 操作,并将得到的结果**取反**。 34 | 35 | 放在这里有点吓人的小知识: 36 | * Python 在进行 `==` 运算时,如果运算符两边的 `__eq__` 都失效了,解释器会用两个对象的 id 做比较\_(:з」∠)\_。_书中用了“最后一搏”这个词…真的有点吓人。_ 37 | 38 | ## 运算符分派 39 | 有的时候,运算符另一边的对象可能会出现多种类型:比如对向量做乘法时,另外一个操作数可能是向量,也可能是一个标量。此时,需要在方法实现中,根据操作数的类型进行分派。 40 | 此时有两种选择: 41 | 1. 尝试直接运算,如果有问题,捕获 `TypeError` 异常; 42 | 2. 在运算前使用 `isinstance` 进行类型判断,在收到可接受类型时在进行运算。 43 | 判断类型时,应进行鸭子类型的判断。应该使用 `isinstance(other, numbers.Integral)`,而不是用 `isinstance(other, int)`,这是之前的知识点。 44 | 45 | 不过,在类上定义方法时,是不能用 `functools.singledispatch` 进行单分派的,因为第一个参数是 `self`,而不是 `o`. 46 | 47 | 48 | ```python 49 | # 一个就地运算符的错误示范 50 | class T: 51 | def __init__(self, s): 52 | self.s = s 53 | 54 | def __str__(self): 55 | return self.s 56 | 57 | def __add__(self, o): 58 | return self.s + o 59 | 60 | def __iadd__(self, o): 61 | self.s += o 62 | # 这里必须要返回一个引用,用于传给 += 左边的引用变量 63 | # return self 64 | 65 | 66 | t = T('1') 67 | t1 = t 68 | w = t + '2' 69 | print(w, type(w)) 70 | t += '2' # t = t.__iadd__('2') 71 | print(t, type(t)) # t 被我们搞成了 None 72 | print(t1, type(t1)) 73 | ``` 74 | 75 | 12 76 | None 77 | 12 78 | 79 | -------------------------------------------------------------------------------- /markdown/第14章:可迭代的对象、迭代器和生成器.md: -------------------------------------------------------------------------------- 1 | 2 | # 可迭代的对象、迭代器和生成器 3 | 4 | > 当我在自己的程序中发现用到了模式,我觉得这就表明某个地方出错了。程序的形式应该仅仅反映它所要解决的问题。代码中其他任何外加的形式都是一个信号,(至少对我来说)表明我对问题的抽象还不够深——这通常意味着自己正在手动完成事情,本应该通过写代码来让宏的扩展自动实现。 5 | > ——Paul Graham, Lisp 黑客和风险投资人 6 | 7 | Python 内置了迭代器模式,用于进行**惰性运算**,按需求一次获取一个数据项,避免不必要的提前计算。 8 | 9 | 迭代器在 Python 中并不是一个具体类型的对象,更多地使指一个具体**协议**。 10 | 11 | ## 迭代器协议 12 | Python 解释器在迭代一个对象时,会自动调用 `iter(x)`。 13 | 内置的 `iter` 函数会做以下操作: 14 | 1. 检查对象是否实现了 `__iter__` 方法(`abc.Iterable`),若实现,且返回的结果是个迭代器(`abc.Iterator`),则调用它,获取迭代器并返回; 15 | 2. 若没实现,但实现了 `__getitem__` 方法(`abc.Sequence`),若实现则尝试从 0 开始按顺序获取元素并返回; 16 | 3. 以上尝试失败,抛出 `TypeError`,表明对象不可迭代。 17 | 18 | 判断一个对象是否可迭代,最好的方法不是用 `isinstance` 来判断,而应该直接尝试调用 `iter` 函数。 19 | 20 | 注:可迭代对象和迭代器不一样。从鸭子类型的角度看,可迭代对象 `Iterable` 要实现 `__iter__`,而迭代器 `Iterator` 要实现 `__next__`. 不过,迭代器上也实现了 `__iter__`,用于[返回自身](https://github.com/python/cpython/blob/3.7/Lib/_collections_abc.py#L268)。 21 | 22 | ## 迭代器的具体实现 23 | 《设计模式:可复用面向对象软件的基础》一书讲解迭代器设计模式时,在“适用性”一 节中说: 24 | 迭代器模式可用来: 25 | * 访问一个聚合对象的内容而无需暴露它的内部表示 26 | * 支持对聚合对象的多种遍历 27 | * 为遍历不同的聚合结构提供一个统一的接口(即支持多态迭代) 28 | 29 | 为了“支持多种遍历”,必须能从同一个可迭代的实例中获取多个**独立**的迭代器,而且各个迭代器要能维护自身的内部状态,因此这一模式正确的实现方式是,每次调用 `iter(my_iterable)` 都新建一个独立的迭代器。这就是为什么这个示例需要定义 `SentenceIterator` 类。 30 | 31 | 所以,不应该把 Sentence 本身作为一个迭代器,否则每次调用 `iter(sentence)` 时返回的都是自身,就无法进行多次迭代了。 32 | 33 | 34 | ```python 35 | # 通过实现迭代器协议,让一个对象变得可迭代 36 | import re 37 | from collections import abc 38 | 39 | 40 | class Sentence: 41 | def __init__(self, sentence): 42 | self.sentence = sentence 43 | self.words = re.findall(r'\w+', sentence) 44 | 45 | def __iter__(self): 46 | """返回 iter(self) 的结果""" 47 | return SentenceIterator(self.words) 48 | 49 | 50 | # 推荐的做法是对迭代器对象进行单独实现 51 | class SentenceIterator(abc.Iterator): 52 | def __init__(self, words): 53 | self.words = words 54 | self._index = 0 55 | 56 | def __next__(self): 57 | """调用时返回下一个对象""" 58 | try: 59 | word = self.words[self._index] 60 | except IndexError: 61 | raise StopIteration() 62 | else: 63 | self._index += 1 64 | 65 | return word 66 | 67 | 68 | 69 | sentence = Sentence('Return a list of all non-overlapping matches in the string.') 70 | assert isinstance(sentence, abc.Iterable) # 实现了 __iter__,就支持 Iterable 协议 71 | assert isinstance(iter(sentence), abc.Iterator) 72 | for word in sentence: 73 | print(word, end='·') 74 | ``` 75 | 76 | Return·a·list·of·all·non·overlapping·matches·in·the·string· 77 | 78 | 上面的例子中,我们的 `SentenceIterator` 对象继承自 `abc.Iterator` 通过了迭代器测试。而且 `Iterator` 替我们实现了 `__iter__` 方法。 79 | 但是,如果我们不继承它,我们就需要同时实现 `__next__` 抽象方法和*实际迭代中并不会用到的* `__iter__` 非抽象方法,才能通过 `Iterator` 测试。 80 | 81 | ## 生成器函数 82 | 如果懒得自己写一个迭代器,可以直接用 Python 的生成器函数来在调用 `__iter__` 时生成一个迭代器。 83 | 84 | 注:在 Python 社区中,大家并没有对“生成器”和“迭代器”两个概念做太多区分,很多人是混着用的。不过无所谓啦。 85 | 86 | 87 | ```python 88 | # 使用生成器函数来帮我们创建迭代器 89 | import re 90 | 91 | 92 | class Sentence: 93 | def __init__(self, sentence): 94 | self.sentence = sentence 95 | self.words = re.findall(r'\w+', sentence) 96 | 97 | def __iter__(self): 98 | for word in self.words: 99 | yield word 100 | return 101 | 102 | sentence = Sentence('Return a list of all non-overlapping matches in the string.') 103 | for word in sentence: 104 | print(word, end='·') 105 | ``` 106 | 107 | 108 | ```python 109 | # 使用 re.finditer 来惰性生成值 110 | # 使用生成器表达式(很久没用过了) 111 | import re 112 | 113 | 114 | class Sentence: 115 | def __init__(self, sentence): 116 | self.re_word = re.compile(r'\w+') 117 | self.sentence = sentence 118 | 119 | def __iter__(self): 120 | return (match.group() 121 | for match in self.re_word.finditer(self.sentence)) 122 | 123 | sentence = Sentence('Return a list of all non-overlapping matches in the string.') 124 | for word in sentence: 125 | print(word, end='·') 126 | ``` 127 | 128 | 129 | ```python 130 | # 实用模块 131 | import itertools 132 | 133 | # takewhile & dropwhile 134 | print(list(itertools.takewhile(lambda x: x < 3, [1, 5, 2, 4, 3]))) 135 | print(list(itertools.dropwhile(lambda x: x < 3, [1, 5, 2, 4, 3]))) 136 | # zip 137 | print(list(zip(range(5), range(3)))) 138 | print(list(itertools.zip_longest(range(5), range(3)))) 139 | 140 | # itertools.groupby 141 | animals = ['rat', 'bear', 'duck', 'bat', 'eagle', 'shark', 'dolphin', 'lion'] 142 | # groupby 需要假定输入的可迭代对象已经按照分组标准进行排序(至少同组的元素要连在一起) 143 | print('----') 144 | for length, animal in itertools.groupby(animals, len): 145 | print(length, list(animal)) 146 | print('----') 147 | animals.sort(key=len) 148 | for length, animal in itertools.groupby(animals, len): 149 | print(length, list(animal)) 150 | print('---') 151 | # tee 152 | g1, g2 = itertools.tee('abc', 2) 153 | print(list(zip(g1, g2))) 154 | ``` 155 | 156 | 157 | ```python 158 | # 使用 yield from 语句可以在生成器函数中直接迭代一个迭代器 159 | from itertools import chain 160 | 161 | def my_itertools_chain(*iterators): 162 | for iterator in iterators: 163 | yield from iterator 164 | 165 | chain1 = my_itertools_chain([1, 2], [3, 4, 5]) 166 | chain2 = chain([1, 2, 3], [4, 5]) 167 | print(list(chain1), list(chain2)) 168 | ``` 169 | 170 | [1, 2, 3, 4, 5] [1, 2, 3, 4, 5] 171 | 172 | 173 | `iter` 函数还有一个鲜为人知的用法:传入两个参数,使用常规的函数或任何可调用的对象创建迭代器。这样使用时,第一个参数必须是可调用的对象,用于不断调用(没有参数),产出各个值;第二个值是哨符,这是个标记值,当可调用的对象返回这个值时,触发迭代器抛出 StopIteration 异常,而不产出哨符。 174 | 175 | 176 | ```python 177 | # iter 的神奇用法 178 | # iter(callable, sentinel) 179 | import random 180 | 181 | def rand(): 182 | return random.randint(1, 6) 183 | # 不停调用 rand(), 直到产出一个 5 184 | print(list(iter(rand, 5))) 185 | ``` 186 | -------------------------------------------------------------------------------- /markdown/第15章:上下文管理器和else块.md: -------------------------------------------------------------------------------- 1 | 2 | # 上下文管理和 else 块 3 | > 最终,上下文管理器可能几乎与子程序(subroutine)本身一样重要。目前,我们只了解了上下文管理器的皮毛……Basic 语言有 with 语句,而且很多语言都有。但是,在各种语言中 with 语句的作用不同,而且做的都是简单的事,虽然可以避免不断使 用点号查找属性,但是不会做事前准备和事后清理。不要觉得名字一样,就意味着作用也一样。with 语句是非常了不起的特性。 4 | > ——Raymond Hettinger, 雄辩的 Python 布道者 5 | 6 | 本章讨论的特性有: 7 | * with 语句和上下文管理器 8 | * for、while 和 try 语句的 else 语句 9 | 10 | ## if 语句之外的 else 块 11 | > else 子句的行为如下。 12 | > 13 | > for: 仅当 for 循环运行完毕时(即 for 循环没有被 break 语句中止)才运行 else 块。 14 | > while: 仅当 while 循环因为条件为**假值**而退出时(即 while 循环没有被 break 语句中止)才运行 else 块。 15 | > try: 仅当try 块中没有异常抛出时才运行else 块。官方文档(https://docs.python.org/3/ reference/compound_stmts.html)还指出:“else 子句抛出的异常不会由前面的 except 子句处理。” 16 | 17 | 18 | ## try 和 else 19 | 为了清晰和准确,`try` 块中应该之抛出预期异常的语句。因此,下面这样写更好: 20 | ```python 21 | try: 22 | dangerous_call() 23 | # 不要把 after_call() 放在这里 24 | # 虽然放在这里时的代码运行逻辑是一样的 25 | except OSError: 26 | log('OSError') 27 | else: 28 | after_call() 29 | ``` 30 | 31 | ## 上下文管理器 32 | 上下文管理器可以在 `with` 块中改变程序的上下文,并在块结束后将其还原。 33 | 34 | 35 | ```python 36 | # 上下文管理器与 __enter__ 方法返回对象的区别 37 | 38 | class LookingGlass: 39 | def __enter__(self): 40 | import sys 41 | self.original_write = sys.stdout.write 42 | sys.stdout.write = self.reverse_write 43 | return 'JABBERWOCKY' 44 | 45 | def reverse_write(self, text): 46 | self.original_write(text[::-1]) 47 | 48 | def __exit__(self, exc_type, exc_value, traceback): 49 | import sys 50 | sys.stdout.write = self.original_write 51 | if exc_type is ZeroDivisionError: 52 | print('Please DO NOT divide by zero!') 53 | return True 54 | 55 | with LookingGlass() as what: # 这里的 what 是 __enter__ 的返回值 56 | print('Alice') 57 | print(what) 58 | 59 | print(what) 60 | print('Back to normal') 61 | ``` 62 | 63 | ecilA 64 | YKCOWREBBAJ 65 | JABBERWOCKY 66 | Back to normal 67 | 68 | 69 | 70 | ```python 71 | # contextmanager 的使用 72 | 73 | import contextlib 74 | 75 | @contextlib.contextmanager 76 | def looking_glass(): 77 | import sys 78 | original_write = sys.stdout.write 79 | sys.stdout.write = lambda text: original_write(text[::-1]) 80 | msg = '' 81 | try: 82 | # 在当前上下文中抛出的异常,会通过 yield 语句抛出 83 | yield 'abcdefg' 84 | except ZeroDivisionError: 85 | msg = 'Please DO NOT divide by zero!' 86 | finally: 87 | # 为了避免上下文内抛出异常导致退出失败 88 | # 所以退出上下文时一定要使用 finally 语句 89 | sys.stdout.write = original_write 90 | if msg: 91 | print(msg) 92 | 93 | # 写完以后感觉这个用法跟 pytest.fixture 好像啊 94 | 95 | with looking_glass() as what: 96 | print('ahhhhh') 97 | print(what) 98 | 99 | print(what) 100 | ``` 101 | 102 | hhhhha 103 | gfedcba 104 | abcdefg 105 | 106 | 107 | 此外,[`contextlib.ExitStack`](https://docs.python.org/3/library/contextlib.html#contextlib.ExitStack) 在某些需要进入未知多个上下文管理器时可以方便管理所有的上下文。具体使用方法可以看文档中的示例。 108 | -------------------------------------------------------------------------------- /markdown/第16章:协程.md: -------------------------------------------------------------------------------- 1 | 2 | # 协程 3 | > 如果 Python 书籍有一定的指导作用,那么(协程就是)文档最匮乏、最鲜为人知的 Python 特性,因此表面上看是最无用的特性。 4 | > ——David Beazley, Python 图书作者 5 | 6 | 在“生成器”章节中我们认识了 `yield` 语句。但 `yield` 的作用不只是在生成器运行过程中**返回**一个值,还可以从调用方拿回来一个值(`.send(datum)`),甚至一个异常(`.throw(exc)`)。 7 | 由此依赖,`yield` 语句就成为了一种流程控制工具,使用它可以实现协作式多任务:协程可以把控制器让步给中心调度程序,从而激活其它的协程。 8 | 9 | 从根本上把 yield 视作控制流程的方式,这样就好理解协程了。 10 | 11 | 本章涵盖以下话题: 12 | * 生成器作为协程使用时的行为和状态 13 | * 使用装饰器自动预激协程 14 | * 调用方如何使用生成器对象的 `.close()` 和 `.throw(...)` 方法控制协程 15 | * 协程终止时如何返回值 16 | * `yield from` 新句法的用途和语义 17 | * 使用案例——使用协程管理仿真系统中的并发活动 18 | 19 | 协程的四种状态: 20 | * `GEN_CREATED`: 等待开始执行 21 | * `GEN_RUNNING`: 解释器正在执行 22 | * `GEN_SUSPENDED`: 在 `yield` 表达式处暂停 23 | * `GEN_CLOSED`: 执行结束 24 | 25 | 26 | ```python 27 | # 最简单的协程使用演示 28 | from inspect import getgeneratorstate 29 | 30 | def simple_coroutine(): 31 | # GEN_RUNNING 状态 32 | print("Coroutine started") 33 | x = yield 34 | print("Couroutine received:", x) 35 | 36 | my_coro = simple_coroutine() 37 | print(getgeneratorstate(my_coro)) # GEN_CREATED 38 | next(my_coro) # “预激”(prime)协程,使它能够接收来自外部的值 39 | print(getgeneratorstate(my_coro)) # GEN_SUSPENDED 40 | try: 41 | my_coro.send(42) 42 | except StopIteration as e: 43 | print('StopIteration') 44 | print(getgeneratorstate(my_coro)) # GEN_CLOSED 45 | ``` 46 | 47 | 48 | ```python 49 | # 产出多个值的协程 50 | def async_sum(a=0): 51 | s = a 52 | while True: 53 | n = yield s 54 | s += n 55 | 56 | asum = async_sum() 57 | next(asum) 58 | for i in range(1, 11): 59 | print(i, asum.send(i)) 60 | asum.close() # 如果协程不会自己关闭,我们还可以手动终止协程 61 | asum.send(11) 62 | ``` 63 | 64 | 1 1 65 | 2 3 66 | 3 6 67 | 4 10 68 | 5 15 69 | 6 21 70 | 7 28 71 | 8 36 72 | 9 45 73 | 10 55 74 | 75 | 76 | 77 | --------------------------------------------------------------------------- 78 | 79 | StopIteration Traceback (most recent call last) 80 | 81 | in () 82 | 11 print(i, asum.send(i)) 83 | 12 asum.close() # 如果协程不会自己关闭,我们还可以手动终止协程 84 | ---> 13 asum.send(11) 85 | 86 | 87 | StopIteration: 88 | 89 | 90 | 协程手动终止的注意事项: 91 | 调用 `gen.closed()` 后,生成器内的 `yield` 语句会抛出 `GeneratorExit` 异常。如果生成器没有处理这个异常,或者抛出了 `StopIteration` 异常(通常是指运行到结尾),调用方不会报错。 92 | 如果收到 `GeneratorExit` 异常,生成器一定不能产出值,否则解释器会抛出 `RuntimeError` 异常。生成器抛出的其他异常会向上冒泡,传给调用方。 93 | 94 | 协程内异常处理的示例见[官方示例 Repo](https://github.com/fluentpython/example-code/blob/master/16-coroutine/coro_finally_demo.py)。 95 | 96 | 97 | ## yield from 98 | 在协程中,`yield from` 语句的主要功能是打开双向通道,把外层的调用方与内层的子生成器连接起来,这样二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程中添加大量处理异常的样板代码。 99 | 这种夹在中间的生成器,我们称它为“委派生成器”。 100 | 101 | 子生成器迭代结束后返回(`return`)的值,会交给 `yield from` 函数。 102 | 103 | 注意:`yield from` 语句会预激生成器,所以与用来预激生成器的装饰器不能放在一起用,否则会出问题。 104 | 105 | 106 | ```python 107 | # 委派生成器 108 | def async_sum(a=0): 109 | s = a 110 | while True: 111 | try: 112 | n = yield s 113 | except Exception as e: 114 | print('Caught exception', e) 115 | return s 116 | s += n 117 | 118 | def middleware(): 119 | x = yield from async_sum() 120 | print('Final result:', x) 121 | 122 | asum = middleware() 123 | next(asum) 124 | for i in range(1, 11): 125 | print(asum.send(i)) 126 | 127 | _ = asum.throw(ValueError) 128 | ``` 129 | 130 | 1 131 | 3 132 | 6 133 | 10 134 | 15 135 | 21 136 | 28 137 | 36 138 | 45 139 | 55 140 | Caught exception 141 | Final result: 55 142 | 143 | 144 | 145 | --------------------------------------------------------------------------- 146 | 147 | StopIteration Traceback (most recent call last) 148 | 149 | in () 150 | 19 print(asum.send(i)) 151 | 20 152 | ---> 21 _ = asum.throw(ValueError) 153 | 154 | 155 | StopIteration: 156 | 157 | 158 | 关于“任务式”协程,书中给出了一个[简单的例子](https://github.com/fluentpython/example-code/blob/master/16-coroutine/taxi_sim.py),用于执行[离散事件仿真](https://zhuanlan.zhihu.com/p/22689081),仔细研究一下可以对协程有个简单的认识。 159 | -------------------------------------------------------------------------------- /markdown/第17章:使用期物处理并发.md: -------------------------------------------------------------------------------- 1 | 2 | # 使用期物处理并发 3 | > 抨击线程的往往是系统程序员,他们考虑的使用场景对一般的应用程序员来说,也许一生都不会遇到……应用程序员遇到的使用场景,99% 的情况下只需知道如何派生一堆独立的线程,然后用队列收集结果。 4 | > Michele Simionato, 深度思考 Python 的人 5 | 6 | 本章主要讨论 `concurrent.futures` 模块,并介绍“期物”(future)的概念。 7 | 8 | 我们在进行 IO 密集型并发编程(如批量下载)时,经常会考虑使用多线程场景来替代依序下载的方案,以提高下载效率。 9 | 在 IO 密集型任务中,如果代码写的正确,那么不管使用哪种并发策略(使用线程或 `asyncio` 包),吞吐量都要比依序执行的代码高很多。 10 | 11 | ## 期物 12 | 期物(Future)表示“**将要**执行并返回结果的任务”,这个概念与 JavaScript 的 `Promise` 对象较为相似。 13 | 14 | Python 3.4 起,标准库中有两个 Future 类:`concurrent.futures.Future` 和 `asyncio.Future`。这两个类的作用相同:`Future` 类的实例表示可能已经完成或尚未完成的延迟计算。 15 | 通常情况下自己不应该创建期物或改变期物的状态,而只能由并发框架实例化。 16 | 我们将某个任务交给并发框架后,这个任务将会由框架来进行调度,我们无法改变它的状态,也不能控制计算任务何时结束。 17 | 18 | 19 | ```python 20 | # 简单的期物用法 21 | import time 22 | from concurrent import futures 23 | 24 | 25 | def fake_download(url): 26 | time.sleep(1) # 这里用的是多线程,所以可以直接考虑 sleep 27 | return url 28 | 29 | 30 | def download_many(url_list): 31 | with futures.ThreadPoolExecutor(max_workers=2) as executor: 32 | to_do = [] 33 | for url in url_list: 34 | future = executor.submit(fake_download, url) 35 | to_do.append(future) 36 | print(f"Scheduled for {url}: {future}") # 因为 worker 数量有限,所以会有一个 future 处于 pending 状态 37 | results = [] 38 | for future in futures.as_completed(to_do): 39 | result = future.result() 40 | print(f'{future} result: {result}') 41 | results.append(result) 42 | return results 43 | 44 | download_many(["https://www.baidu.com/", "https://www.google.com/", 45 | "https://twitter.com/"]) 46 | ``` 47 | 48 | Scheduled for https://www.baidu.com/: 49 | Scheduled for https://www.google.com/: 50 | Scheduled for https://twitter.com/: 51 | result: https://www.baidu.com/ 52 | result: https://www.google.com/ 53 | result: https://twitter.com/ 54 | 55 | 56 | 57 | 58 | 59 | ['https://www.baidu.com/', 'https://www.google.com/', 'https://twitter.com/'] 60 | 61 | 62 | 63 | `ThreadExecutor` 使用多线程处理并发。在程序被 IO 阻塞时,Python 标准库会释放 GIL,以允许其它线程运行。 64 | 所以,GIL 的存在并不会对 IO 密集型多线程并发造成太大影响。 65 | 66 | 67 | `concurrent` 包中提供了 `ThreadPoolExecutor` 和 `ProcessPoolExecutor` 类,分别对应多线程和多进程模型。 68 | 关于两种模型的使用及推荐并发数,我们有一个经验: 69 | * CPU 密集型任务,推荐使用多进程模型,以利用 CPU 的多个核心,`max_workers` 应设置为 CPU 核数; 70 | * IO 密集型任务,多核 CPU 不会提高性能,所以推荐使用多线程模型,可以省下多进程带来的资源开销,`max_workers` 可以尽可能设置多一些。 71 | 72 | 73 | ```python 74 | # Executor.map 75 | # 并发运行多个可调用的对象时,可以使用 map 方法 76 | 77 | import time 78 | from concurrent import futures 79 | 80 | 81 | def fake_download(url): 82 | time.sleep(1) # 这里用的是多线程,所以可以直接考虑 sleep 83 | print(f'[{time.strftime("%H:%M:%S")}] Done with {url}\n', end='') 84 | return url 85 | 86 | 87 | def download_many(url_list): 88 | with futures.ThreadPoolExecutor(max_workers=3) as executor: 89 | results = executor.map(fake_download, url_list) 90 | return results 91 | 92 | results = download_many(list(range(5))) 93 | print('Results:', list(results)) 94 | ``` 95 | 96 | [15:36:08] Done with 0 97 | [15:36:08] Done with 1 98 | [15:36:08] Done with 2 99 | [15:36:09] Done with 3 100 | [15:36:09] Done with 4 101 | Results: [0, 1, 2, 3, 4] 102 | 103 | 104 | `map` 的使用可能更方便一点,但 `futures.as_completed` 则更灵活:支持不同的运算方法及参数,甚至支持来自不同 `Executor` 的 `future`. 105 | 106 | ## 总结 107 | 15 年的时候看过一篇文章叫[《一行 Python 实现并行化》](https://segmentfault.com/a/1190000000414339),里面讲述了如何利用 `multiprocessing.Pool.map`(或者 `multiprocessing.dummy.Pool.map`)快速实现多进程 / 多线程模型的并发任务处理。 108 | 109 | [`concurrent.furures`](https://docs.python.org/3/library/concurrent.futures.html) 模块于 Python 3.2 版本引入,它把线程、进程和队列是做服务的基础设施,无须手动进行管理即可轻松实现并发任务。同时,这个包引入了“期物”的概念,可以对并发任务更加规范地进行注册及管理。 110 | -------------------------------------------------------------------------------- /markdown/第18章:使用asyncio包处理并发.md: -------------------------------------------------------------------------------- 1 | 2 | # 使用 asyncio 包处理并发 3 | > 并发是指一次处理多件事。 4 | > 并行是指一次做多件事。 5 | > 二者不同,但是有联系。 6 | > 一个关于结构,一个关于执行。 7 | > 并发用于制定方案,用来解决可能(但未必)并行的问题。 8 | > ——Rob Pike, Go 语言的创造者之一 9 | 10 | 本章介绍 `asyncio` 包,这个包使用事件循环驱动的协程实现并发。 11 | `asyncio` 包支持 Python 3.3 及以上版本,并在 Python 3.4 中正式成为标准库。 12 | 13 | 本章讨论以下话题: 14 | * 对比一个简单的多线程程序和对应的 asyncio 版,说明多线程和异步任务之间的关系 15 | * asyncio.Future 类与 concurrent.futures.Future 类之间的区别 16 | * 第 17 章中下载国旗那些示例的异步版 17 | * 摒弃线程或进程,如何使用异步编程管理网络应用中的高并发 18 | * 在异步编程中,与回调相比,协程显著提升性能的方式 19 | * 如何把阻塞的操作交给线程池处理,从而避免阻塞事件循环 20 | * 使用 asyncio 编写服务器,重新审视 Web 应用对高并发的处理方式 21 | * 为什么 asyncio 已经准备好对 Python 生态系统产生重大影响 22 | 23 | 由于 Python 3.5 支持了 `async` 和 `await`,所以书中的 ` @asyncio.coroutine` 和 `yield from` 均会被替换成 `async def` 和 `await`. 24 | 同时,[官方 Repo](https://github.com/fluentpython/example-code/tree/master/18b-async-await) 也使用 `async` 和 `await` 重新实现了书中的实例。 25 | 26 | ## 协程的优点 27 | 多线程的代码,很容易在任务执行过程中被挂起;而协程的代码是手动挂起的,只要代码没有运行到 `await`,调度器就不会挂起这个协程。 28 | 29 | 30 | ```python 31 | # 基于 asyncio 的协程并发实现 32 | # https://github.com/fluentpython/example-code/blob/master/18b-async-await/spinner_await.py 33 | # 在 Jupyter notebook 中运行该代码会报错,所以考虑把它复制出去单独运行。 34 | # 见 https://github.com/jupyter/notebook/issues/3397 35 | import asyncio 36 | import itertools 37 | import sys 38 | 39 | 40 | async def spin(msg): 41 | write, flush = sys.stdout.write, sys.stdout.flush 42 | for char in itertools.cycle('|/-\\'): 43 | status = char + ' ' + msg 44 | write(status) 45 | flush() 46 | write('\x08' * len(status)) 47 | try: 48 | await asyncio.sleep(.1) 49 | except asyncio.CancelledError: 50 | break 51 | write(' ' * len(status) + '\x08' * len(status)) # 使用空格和 '\r' 清空本行 52 | 53 | 54 | async def slow_function(): 55 | # pretend waiting a long time for I/O 56 | await asyncio.sleep(3) 57 | return 42 58 | 59 | 60 | async def supervisor(): 61 | spinner = asyncio.ensure_future(spin('thinking!')) # 执行它,但不需要等待它返回结果 62 | print('spinner object:', spinner) 63 | result = await slow_function() # 挂起当前协程,等待 slow_function 完成 64 | spinner.cancel() # 从 await 中抛出 CancelledError,终止协程 65 | return result 66 | 67 | 68 | def main(): 69 | loop = asyncio.get_event_loop() 70 | result = loop.run_until_complete(supervisor()) 71 | loop.close() 72 | print('Answer:', result) 73 | 74 | 75 | main() 76 | ``` 77 | 78 | ## 基于协程的并发编程用法 79 | 定义协程: 80 | 1. 使用 `async def` 定义协程任务; 81 | 2. 在协程中使用 `await` 挂起当前协程,唤起另一个协程,并等待它返回结果; 82 | 3. 处理完毕后,使用 `return` 返回当前协程的结果 83 | 84 | 运行协程:不要使用 `next` 或 `.send()` 来操控协程,而是把它交给 `event_loop` 去完成。 85 | ```python 86 | loop = asyncio.get_event_loop() 87 | result = loop.run_until_complete(supervisor()) 88 | ``` 89 | 90 | 注:`@asyncio.coroutine` 并**不会预激协程**。 91 | 92 | ## 使用协程进行下载 93 | [aiohttp](https://aiohttp.readthedocs.io/en/stable/) 库提供了基于协程的 HTTP 请求功能。 94 | 书中提供的并行下载国旗的简单示例可以在[这里](https://github.com/fluentpython/example-code/blob/master/17-futures/countries/flags_await.py)看到。 95 | 完整示例在下载国旗的同时还下载了国家的元信息,并考虑了出错处理及并发数量,可以在[这里](https://github.com/fluentpython/example-code/blob/master/17-futures/countries/flags3_asyncio.py)看到。 96 | 97 | `asyncio.Semaphore` 提供了协程层面的[信号量](https://zh.wikipedia.org/zh-cn/%E4%BF%A1%E5%8F%B7%E9%87%8F)服务,我们可以使用这个信号量来[限制并发数](https://github.com/fluentpython/example-code/blob/master/17-futures/countries/flags3_asyncio.py#L61)。 98 | 99 | ```python 100 | with (await semaphore): # semaphore.acquire 101 | image = await get_flag(base_url, cc) 102 | # semaphore.release 103 | ``` 104 | 105 | ## 在协程中避免阻塞任务 106 | 文件 IO 是一个非常耗时的操作,但 asyncio 并没有提供基于协程的文件操作,所以我们可以在协程中使用 `run_in_executor` 将任务交给 `Executor` 去执行[异步操作](https://github.com/fluentpython/example-code/blob/master/17-futures/countries/flags3_asyncio.py#L74)。 107 | 108 | 注:[aiofiles](https://github.com/Tinche/aiofiles) 实现了基于协程的文件操作。 109 | 110 | ## 使用 aiohttp 编写 Web 服务器 111 | 廖雪峰写了个关于 asyncio 的[小教程](https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014320981492785ba33cc96c524223b2ea4e444077708d000)。 112 | 如果需要实现大一点的应用,可以考虑使用 [Sanic](https://sanic.readthedocs.io/en/latest/) 框架。基于这个框架的 Web 应用写法和 Flask 非常相似。 113 | 114 | ## 使用 asyncio 做很多很多事情 115 | GitHub 的 [aio-libs](https://github.com/aio-libs) 组织提供了很多异步驱动,比如 MySQL, Zipkin 等。 116 | -------------------------------------------------------------------------------- /markdown/第19章:动态属性和特性.md: -------------------------------------------------------------------------------- 1 | 2 | # 动态属性和特性 3 | > 特性至关重要的地方在于,特性的存在使得开发者可以非常安全并且确定可行地将公共数据属性作为类的公共接口的一部分开放出来。 4 | > ——Alex Martelli, Python 贡献者和图书作者 5 | 6 | 本章内容: 7 | * 特性(property) 8 | * 动态属性存取(`__getattr__` 和 `__setattr__`) 9 | * 对象的动态创建(`__new__`) 10 | 11 | ## 特性 12 | Python 特性(property)可以使我们在不改变接口的前提下,使用**存取方法**修改数据属性。 13 | 14 | 15 | ```python 16 | # property 17 | class A: 18 | def __init__(self): 19 | self._val = 0 20 | 21 | @property 22 | def val(self): 23 | return self._val 24 | 25 | @val.setter 26 | def val(self, val): 27 | print('Set val', val) 28 | self._val = val 29 | 30 | @val.deleter 31 | def val(self): 32 | print('Val deleted!') 33 | 34 | a = A() 35 | assert a.val == 0 36 | a.val = 1 37 | assert a.val == 1 38 | del a.val 39 | assert a.val == 1 # del val 只是触发了 deleter 方法,再取值时还会执行 val 的 getter 函数 40 | ``` 41 | 42 | Set val 1 43 | Val deleted! 44 | 45 | 46 | ## 动态属性 47 | 当访问对象的某个属性时,如果对象没有这个属性,解释器会尝试调用对象的 `__attr__` 方法。 48 | 但是注意,这个属性名必须是一个合法的标识符。 49 | 50 | 注:`__getattr__` 和 `__getattribute__` 的区别在于,`__getattribute__` 在每次访问对象属性时都会触发,而 `__getattr__` 只在该对象没有这个属性的时候才会触发。 51 | 52 | 53 | ```python 54 | class B: 55 | a = 1 56 | def __getattr__(self, attr): 57 | print('getattr', attr) 58 | return attr 59 | 60 | def __getattribute__(self, attr): 61 | print('getattribute', attr) 62 | return super().__getattribute__(attr) 63 | 64 | b = B() 65 | print(b.a, b.b) 66 | ``` 67 | 68 | getattribute a 69 | getattribute b 70 | getattr b 71 | 1 b 72 | 73 | 74 | ## __new__ 方法 75 | `__new__` 方法是类上的一个特殊方法,用于生成一个新对象。 76 | 与 `__init__` 不同,`__new__` 方法必须要返回一个对象,而 `__init__` 则不需要。 77 | 调用 `A.__new__` 时,返回的对象不一定需要是 A 类的实例。 78 | 79 | 80 | ```python 81 | # 对象构造过程示意 82 | class Foo: 83 | # __new__ 是一个特殊方法,所以不需要 @classmethod 装饰器 84 | def __new__(cls, arg): 85 | if arg is None: 86 | return [] 87 | return super().__new__(cls) # 用 object.__new__ 生成对象后开始执行 __init__ 函数 88 | 89 | def __init__(self, arg): 90 | print('arg:', arg) 91 | self.arg = arg 92 | 93 | 94 | def object_maker(the_class, some_arg): 95 | new_object = the_class.__new__(the_class, some_arg) 96 | if isinstance(new_object, the_class): 97 | the_class.__init__(new_object, some_arg) 98 | return new_object 99 | 100 | # 下述两个语句的作用基本等效 101 | x = Foo('bar') 102 | y = object_maker(Foo, 'bar') 103 | assert x.arg == y.arg == 'bar' 104 | n = Foo(None) 105 | assert n == [] 106 | ``` 107 | 108 | arg bar 109 | arg bar 110 | 111 | 112 | ## 杂谈 113 | [shelve](https://docs.python.org/3/library/shelve.html) 是 Python 自带的、类 `dict` 的 KV 数据库,支持持久化存储。 114 | 115 | ```python 116 | import shelve 117 | 118 | d = shelve.open(filename) # open -- file may get suffix added by low-level 119 | # library 120 | 121 | d[key] = data # store data at key (overwrites old data if 122 | # using an existing key) 123 | data = d[key] # retrieve a COPY of data at key (raise KeyError 124 | # if no such key) 125 | del d[key] # delete data stored at key (raises KeyError 126 | # if no such key) 127 | 128 | flag = key in d # true if the key exists 129 | klist = list(d.keys()) # a list of all existing keys (slow!) 130 | 131 | # as d was opened WITHOUT writeback=True, beware: 132 | d['xx'] = [0, 1, 2] # this works as expected, but... 133 | d['xx'].append(3) # *this doesn't!* -- d['xx'] is STILL [0, 1, 2]! 134 | 135 | # having opened d without writeback=True, you need to code carefully: 136 | temp = d['xx'] # extracts the copy 137 | temp.append(5) # mutates the copy 138 | d['xx'] = temp # stores the copy right back, to persist it 139 | 140 | # or, d=shelve.open(filename,writeback=True) would let you just code 141 | # d['xx'].append(5) and have it work as expected, BUT it would also 142 | # consume more memory and make the d.close() operation slower. 143 | 144 | d.close() # close it 145 | ``` 146 | 架子(shelve)上放一堆泡菜(pickle)坛子…没毛病。 147 | -------------------------------------------------------------------------------- /markdown/第20章:属性描述符.md: -------------------------------------------------------------------------------- 1 | 2 | # 属性描述符 3 | > 学会描述符之后,不仅有更多的工具集可用,还会对 Python 的运作方式有更深入的理解,并由衷赞叹 Python 设计的优雅。 4 | > ——Raymond Hettinger, Python 核心开发者和专家 5 | 6 | 本章的话题是描述符。 7 | 描述符是实现了特定协议的类,这个协议包括 `__get__`、`__set__`、和 `__delete__` 方法。 8 | 9 | 有了它,我们就可以在类上定义一个托管属性,并把所有对实例中托管属性的读写操作交给描述符类去处理。 10 | 11 | 12 | ```python 13 | # 描述符示例:将一个属性托管给一个描述符类 14 | class CharField: # 描述符类 15 | def __init__(self, field_name): 16 | self.field_name = field_name 17 | 18 | def __get__(self, instance, storage_cls): 19 | print('__get__', instance, storage_cls) 20 | if instance is None: # 直接在类上访问托管属性时,instance 为 None 21 | return self 22 | return instance[self.field_name] 23 | 24 | def __set__(self, instance, value): 25 | print('__set__', instance, value) 26 | if not isinstance(value, str): 27 | raise TypeError('Value should be string') 28 | instance[self.field_name] = value 29 | 30 | 31 | class SomeModel: # 托管类 32 | name = CharField('name') # 描述符实例,也是托管类中的托管属性 33 | 34 | def __init__(self, **kwargs): 35 | self._dict = kwargs # 出巡属性,用于存储属性 36 | 37 | def __getitem__(self, item): 38 | return self._dict[item] 39 | 40 | def __setitem__(self, item, value): 41 | self._dict[item] = value 42 | 43 | 44 | 45 | print(SomeModel.name) 46 | d = SomeModel(name='some name') 47 | print(d.name) 48 | d.name = 'another name' 49 | print(d.name) 50 | try: 51 | d.name = 1 52 | except Exception as e: 53 | print(repr(e)) 54 | ``` 55 | 56 | __get__ None 57 | <__main__.CharField object at 0x063AF1F0> 58 | __get__ <__main__.SomeModel object at 0x063AF4B0> 59 | some name 60 | __set__ <__main__.SomeModel object at 0x063AF4B0> another name 61 | __get__ <__main__.SomeModel object at 0x063AF4B0> 62 | another name 63 | __set__ <__main__.SomeModel object at 0x063AF4B0> 1 64 | TypeError('Value should be string') 65 | 66 | 67 | ## 描述符的种类 68 | 根据描述符上实现的方法类型,我们可以把描述符分为**覆盖型描述符**和**非覆盖型描述符**。 69 | 70 | 实现 `__set__` 方法的描述符属于覆盖型描述符,因为虽然描述符是类属性,但是实现 `__set__` 方法的话,会覆盖对实例属性的赋值操作。 71 | 而没有实现 `__set__` 方法的描述符是非覆盖型描述符。对实例的托管属性赋值,则会覆盖掉原有的描述符属性,此后再访问该属性时,将不会触发描述符的 `__get__` 操作。如果想恢复原有的描述符行为,则需要用 `del` 把覆盖掉的属性删除。 72 | 73 | 具体可以看[官方 Repo 的例子](https://github.com/fluentpython/example-code/blob/master/20-descriptor/descriptorkinds.py)。 74 | 75 | ## 描述符的用法建议 76 | * 如果只想实现一个只读描述符,可以考虑使用 `property` 而不是自己去实现描述符; 77 | * 只读描述符必须有 `__set__` 方法,在方法内抛出 `AttributeError`,防止属性在写时被覆盖; 78 | * 用于验证的描述符可以只有 `__set__` 方法:通过验证后,可以修改 `self.__dict__[key]` 来将属性写入对象; 79 | * 仅有 `__get__` 方法的描述符可以实现高效缓存; 80 | * 非特殊的方法可以被实例属性覆盖。 81 | -------------------------------------------------------------------------------- /markdown/第21章:类元编程.md: -------------------------------------------------------------------------------- 1 | 2 | # 类元编程 3 | > (元类)是深奥的知识,99% 的用户都无需关注。如果你想知道是否需要使用元类,我告诉你,不需要(真正需要使用元类的人确信他们需要,无需解释原因)。 4 | > ——Tim Peters, Timsort 算法的发明者,活跃的 Python 贡献者 5 | 6 | 元类功能强大,但是难以掌握。使用元类可以创建具有某种特质的全新类中,例如我们见过的抽象基类。 7 | 8 | 本章还会谈及导入时和运行时的区别。 9 | 10 | 注:除非开发框架,否则不要编写元类。 11 | 12 | ## 类工厂函数 13 | 像 `collections.namedtuple` 一样,类工厂函数的返回值是一个类,这个类的特性(如属性名等)可能由函数参数提供。 14 | 可以参见[官方示例](https://github.com/fluentpython/example-code/blob/master/21-class-metaprog/factories.py)。 15 | 原理:使用 `type` 构造方法,可以构造一个新的类。 16 | 17 | 18 | ```python 19 | help(type) 20 | ``` 21 | 22 | ## 元类 23 | 元类是一个类,但因为它继承自 `type`,所以可以通过它生成一个类。 24 | 25 | 在使用元类时,将会调用元类的 `__new__` 和 `__init__`,为类添加更多特性。 26 | 这一步会在**导入**时完成,而不是在类进行实例化时。 27 | 28 | 元类的作用举例: 29 | * 验证属性 30 | * 一次性把某个/种装饰器依附到多个方法上(记得以前写过一个装饰器来实现这个功能,因为那个类的 `metaclass` 被占了) 31 | * 序列化对象或转换数据 32 | * 对象关系映射 33 | * 基于对象的持久存储 34 | * 动态转换使用其他语言编写的结构 35 | 36 | ### Python 中各种类的关系 37 | > `object` 类和 `type` 类的关系很独特:`object` 是 `type` 的实例,而 `type` 是 `object` 的子类。 38 | 所有类都直接或间接地是 `type` 的实例,不过只有元类同时也是 `type` 的子类,因为**元类从 `type` 类继承了构造类的能力**。 39 | 40 | 这里面的关系比较复杂,简单理一下 41 | * 实例关系 42 | * `type` 可以产出类,所以 `type` 的实例是类(`isinstance(int, type)`); 43 | * 元类继承自 `type` 类,所以元类也具有生成类实例的**能力**(`isinstance(Sequence, ABCMeta)`) 44 | * 继承关系 45 | * Python 中一切皆对象,所以所有类都是 `object` 的子类(`object in int.__mro__`) 46 | * 元类要**继承**自 `type` 对象,所以元类是 `type` 的子类(`type in ABCMeta.__mro__`) 47 | 48 | 49 | ```python 50 | # 构建一个元类 51 | class SomeMeta(type): 52 | def __init__(cls, name, bases, dic): 53 | # 这里 __init__ 的是 SomeClass,因为它是个类,所以我们直接用 cls 而不是 self 来命名它 54 | print('Metaclass __init__') 55 | # 为我们的类添加一个**类**属性 56 | cls.a = 1 57 | 58 | class SomeClass(metaclass=SomeMeta): 59 | # 在解释器编译这个类的最后,SomeMeta 的 __init__ 将被调用 60 | print('Enter SomeClass') 61 | def __init__(self, val): 62 | # 这个函数在 SomeClass 实例化时才会被调用 63 | self.val = val 64 | 65 | 66 | assert SomeClass.a == 1 # 元类为类添加的类属性 67 | sc = SomeClass(2) 68 | assert sc.val == 2 69 | assert sc.a == 1 70 | print(sc.__dict__, SomeClass.__dict__) 71 | ``` 72 | 73 | Enter SomeClass 74 | Metaclass __init__ 75 | {'val': 2} {'__module__': '__main__', '__init__': , '__dict__': , '__weakref__': , '__doc__': None, 'a': 1} 76 | 77 | 78 | 关于类构造过程,可以参见官方 Repo 中的[代码执行练习(evaltime)部分](https://github.com/fluentpython/example-code/tree/master/21-class-metaprog)。 79 | 80 | 81 | ```python 82 | # 用元类构造描述符 83 | from collections import OrderedDict 84 | 85 | 86 | class Field: 87 | def __get__(self, instance, cls): 88 | if instance is None: 89 | return self 90 | name = self.__name__ 91 | return instance._value_dict.get(name) 92 | 93 | def __set__(self, instance, val): 94 | name = self.__name__ # 通过 _entity_name 属性拿到该字段的名称 95 | instance._value_dict[name] = val 96 | 97 | 98 | class DesNameMeta(type): 99 | @classmethod 100 | def __prepare__(cls, name, bases): 101 | """ 102 | Python 3 特有的方法,用于返回一个映射对象 103 | 然后传给 __init__ 的 dic 参数 104 | """ 105 | return OrderedDict() 106 | 107 | def __init__(cls, name, bases, dic): 108 | field_names = [] 109 | for name, val in dic.items(): 110 | if isinstance(val, Field): 111 | val.__name__ = name # 在生成类的时候,将属性名加到了描述符中 112 | field_names.append(name) 113 | cls._field_names = field_names 114 | 115 | 116 | 117 | class NewDesNameMeta(type): # 使用 __new__ 方法构造新类 118 | def __new__(cls, name, bases, dic): 119 | for name, val in dic.items(): 120 | if isinstance(val, Field): 121 | val.__name__ = name 122 | return super().__new__(cls, name, bases, dic) 123 | 124 | 125 | class SomeClass(metaclass=DesNameMeta): 126 | name = Field() 127 | title = Field() 128 | 129 | def __init__(self): 130 | self._value_dict = {} 131 | 132 | def __iter__(self): 133 | """ 134 | 按定义顺序输出属性值 135 | """ 136 | for field in self._field_names: 137 | yield getattr(self, field) 138 | 139 | 140 | assert SomeClass.name.__name__ == 'name' 141 | sc = SomeClass() 142 | sc.name = 'Name' 143 | sc.title = 'Title' 144 | assert sc.name == 'Name' 145 | print(sc._value_dict) 146 | print(list(sc)) 147 | ``` 148 | 149 | {'name': 'Name', 'title': 'Title'} 150 | ['Name', 'Title'] 151 | 152 | 153 | 上面的例子只是演示作用,实际上在设计框架的时候,`SomeClass` 会设计为一个基类(`models.Model`),框架用户只要继承自 `Model` 即可正常使用 `Field` 中的属性名,而无须知道 `DesNameMeta` 的存在。 154 | 155 | ## 类的一些特殊属性 156 | 类上有一些特殊属性,这些属性不会在 `dir()` 中被列出,访问它们可以获得类的一些元信息。 157 | 同时,元类可以更改这些属性,以定制类的某些行为。 158 | 159 | * `cls.__bases__` 160 | 由类的基类组成的元组 161 | * `cls.__qualname__` 162 | 类或函数的限定名称,即从模块的全局作用域到类的点分路径 163 | * `cls.__subclasses__()` 164 | 这个方法返回一个列表,包含类的**直接**子类 165 | * `cls.__mro__` 166 | 类的方法解析顺序,这个属性是只读的,元类无法进行修改 167 | * `cls.mro()` 168 | 构建类时,如果需要获取储存在类属性 __mro__ 中的超类元组,解释器会调用这个方法。元类可以覆盖这个方法,定制要构建的类解析方法的顺序。 169 | 170 | ## 延伸阅读 171 | [`types.new_class`](https://docs.python.org/3/library/types.html#types.new_class) 和 `types.prepare_class` 可以辅助我们进行类元编程。 172 | 173 | 最后附上一个名言警句: 174 | 175 | > 此外,不要在生产代码中定义抽象基类(或元类)……如果你很想这样做,我打赌可能是因为你想“找茬”,刚拿到新工具的人都有大干一场的冲动。如果你能避开这些深奥的概念,你(以及未来的代码维护者)的生活将更愉快,因为代码简洁明了。 176 | > ——Alex Martelli 177 | -------------------------------------------------------------------------------- /第01章:Python 数据类型.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Python 数据类型\n", 8 | "> Guido 对语言设计美学的深入理解让人震惊。我认识不少很不错的编程语言设计者,他们设计出来的东西确实很精彩,但是从来都不会有用户。Guido 知道如何在理论上做出一定妥协,设计出来的语言让使用者觉得如沐春风,这真是不可多得。 \n", 9 | "> ——Jim Hugunin \n", 10 | "> Jython 的作者,AspectJ 的作者之一,.NET DLR 架构师\n", 11 | "\n", 12 | "Python 最好的品质之一是**一致性**:你可以轻松理解 Python 语言,并通过 Python 的语言特性在类上定义**规范的接口**,来支持 Python 的核心语言特性,从而写出具有“Python 风格”的对象。 \n", 13 | "Python 解释器在碰到特殊的句法时,会使用特殊方法(我们称之为魔术方法)去激活一些基本的对象操作。如 `my_c[key]` 语句执行时,就会调用 `my_c.__getitem__` 函数。这些特殊方法名能让你自己的对象实现和支持一下的语言构架,并与之交互:\n", 14 | "* 迭代\n", 15 | "* 集合类\n", 16 | "* 属性访问\n", 17 | "* 运算符重载\n", 18 | "* 函数和方法的调用\n", 19 | "* 对象的创建和销毁\n", 20 | "* 字符串表示形式和格式化\n", 21 | "* 管理上下文(即 `with` 块)" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 10, 27 | "metadata": {}, 28 | "outputs": [ 29 | { 30 | "name": "stdout", 31 | "output_type": "stream", 32 | "text": [ 33 | "52\n", 34 | "Card(rank='3', suit='spades') [Card(rank='7', suit='spades'), Card(rank='7', suit='diamonds'), Card(rank='7', suit='clubs'), Card(rank='7', suit='hearts')]\n", 35 | "Card(rank='6', suit='diamonds')\n" 36 | ] 37 | } 38 | ], 39 | "source": [ 40 | "# 通过实现魔术方法,来让内置函数支持你的自定义对象\n", 41 | "# https://github.com/fluentpython/example-code/blob/master/01-data-model/frenchdeck.py\n", 42 | "import collections\n", 43 | "import random\n", 44 | "\n", 45 | "Card = collections.namedtuple('Card', ['rank', 'suit'])\n", 46 | "\n", 47 | "class FrenchDeck:\n", 48 | " ranks = [str(n) for n in range(2, 11)] + list('JQKA')\n", 49 | " suits = 'spades diamonds clubs hearts'.split()\n", 50 | "\n", 51 | " def __init__(self):\n", 52 | " self._cards = [Card(rank, suit) for suit in self.suits\n", 53 | " for rank in self.ranks]\n", 54 | "\n", 55 | " def __len__(self):\n", 56 | " return len(self._cards)\n", 57 | "\n", 58 | " def __getitem__(self, position):\n", 59 | " return self._cards[position]\n", 60 | "\n", 61 | "deck = FrenchDeck()\n", 62 | "# 实现 __length__ 以支持 len\n", 63 | "print(len(deck))\n", 64 | "# 实现 __getitem__ 以支持下标操作\n", 65 | "print(deck[1])\n", 66 | "print(deck[5::13])\n", 67 | "# 有了这些操作,我们就可以直接对这些对象使用 Python 的自带函数了\n", 68 | "print(random.choice(deck))" 69 | ] 70 | }, 71 | { 72 | "cell_type": "markdown", 73 | "metadata": {}, 74 | "source": [ 75 | "Python 支持的所有魔术方法,可以参见 Python 文档 [Data Model](https://docs.python.org/3/reference/datamodel.html) 部分。\n", 76 | "\n", 77 | "比较重要的一点:不要把 `len`,`str` 等看成一个 Python 普通方法:由于这些操作的频繁程度非常高,所以 Python 对这些方法做了特殊的实现:它可以让 Python 的内置数据结构走后门以提高效率;但对于自定义的数据结构,又可以在对象上使用通用的接口来完成相应工作。但在代码编写者看来,`len(deck)` 和 `len([1,2,3])` 两个实现可能差之千里的操作,在 Python 语法层面上是高度一致的。" 78 | ] 79 | } 80 | ], 81 | "metadata": { 82 | "kernelspec": { 83 | "display_name": "Python 3", 84 | "language": "python", 85 | "name": "python3" 86 | }, 87 | "language_info": { 88 | "codemirror_mode": { 89 | "name": "ipython", 90 | "version": 3 91 | }, 92 | "file_extension": ".py", 93 | "mimetype": "text/x-python", 94 | "name": "python", 95 | "nbconvert_exporter": "python", 96 | "pygments_lexer": "ipython3", 97 | "version": "3.7.0" 98 | } 99 | }, 100 | "nbformat": 4, 101 | "nbformat_minor": 2 102 | } 103 | -------------------------------------------------------------------------------- /第02章:序列构成的数组.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 序列构成的数组\n", 8 | "> 你可能注意到了,之前提到的几个操作可以无差别地应用于文本、列表和表格上。 \n", 9 | "> 我们把文本、列表和表格叫作数据火车……FOR 命令通常能作用于数据火车上。 \n", 10 | "> ——Geurts、Meertens 和 Pemberton \n", 11 | "> *ABC Programmer’s Handbook*" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "* 容器序列 \n", 19 | " `list`、`tuple` 和 `collections.deque` 这些序列能存放不同类型的数据。\n", 20 | "* 扁平序列 \n", 21 | " `str`、`bytes`、`bytearray`、`memoryview` 和 `array.array`,这类序列只能容纳一种类型。\n", 22 | " \n", 23 | "容器序列存放的是它们所包含的任意类型的对象的**引用**,而扁平序列里存放的**是值而不是引用**。换句话说,扁平序列其实是一段连续的内存空间。由此可见扁平序列其实更加紧凑,但是它里面只能存放诸如字符、字节和数值这种基础类型。\n", 24 | "\n", 25 | "序列类型还能按照能否被修改来分类。\n", 26 | "* 可变序列 \n", 27 | " `list`、`bytearray`、`array.array`、`collections.deque` 和 `memoryview`。\n", 28 | "* 不可变序列 \n", 29 | " `tuple`、`str` 和 `bytes`" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "metadata": {}, 36 | "outputs": [], 37 | "source": [ 38 | "# 列表推导式和生成器表达式\n", 39 | "symbols = \"列表推导式\"\n", 40 | "[ord(symbol) for symbol in symbols]\n", 41 | "(ord(symbol) for symbol in symbols)" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": null, 47 | "metadata": {}, 48 | "outputs": [], 49 | "source": [ 50 | "# 因为 pack/unpack 的存在,元组中的元素会凸显出它们的位置信息\n", 51 | "first, *others, last = (1, 2, 3, 4, 5)\n", 52 | "print(first, others, last)\n", 53 | "# 当然后面很多可迭代对象都支持 unpack 了…" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [ 62 | "# namedtuple\n", 63 | "from collections import namedtuple\n", 64 | "\n", 65 | "Point = namedtuple('Point', ['x', 'y'])\n", 66 | "p = Point(1, 2)\n", 67 | "print(p, p.x, p.y)\n", 68 | "# _asdict() 会返回 OrderedDict\n", 69 | "print(p._asdict())" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "metadata": {}, 76 | "outputs": [], 77 | "source": [ 78 | "# 为什么切片(slice)不返回最后一个元素\n", 79 | "a = list(range(6))\n", 80 | "# 使用同一个数即可将列表进行分割\n", 81 | "print(a[:2], a[2:])" 82 | ] 83 | }, 84 | { 85 | "cell_type": "code", 86 | "execution_count": null, 87 | "metadata": {}, 88 | "outputs": [], 89 | "source": [ 90 | "# Ellipsis\n", 91 | "def test(first, xxx, last):\n", 92 | " print(xxx)\n", 93 | " print(type(xxx))\n", 94 | " print(xxx == ...)\n", 95 | " print(xxx is ...)\n", 96 | " return first, last\n", 97 | "\n", 98 | "# ... 跟 None 一样,有点神奇\n", 99 | "print(test(1, ..., 2))" 100 | ] 101 | }, 102 | { 103 | "cell_type": "markdown", 104 | "metadata": {}, 105 | "source": [ 106 | "### bisect 二分查找" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": null, 112 | "metadata": {}, 113 | "outputs": [], 114 | "source": [ 115 | "import bisect\n", 116 | "def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):\n", 117 | " i = bisect.bisect(breakpoints, score)\n", 118 | " return grades[i]\n", 119 | "\n", 120 | "print([grade(score) for score in [33, 99, 77, 70, 89, 90, 100]])\n", 121 | "\n", 122 | "a = list(range(0, 100, 10))\n", 123 | "# 插入并保持有序\n", 124 | "bisect.insort(a, 55)\n", 125 | "print(a)" 126 | ] 127 | }, 128 | { 129 | "cell_type": "markdown", 130 | "metadata": {}, 131 | "source": [ 132 | "### Array\n", 133 | "> 虽然列表既灵活又简单,但面对各类需求时,我们可能会有更好的选择。比如,要存放 1000 万个浮点数的话,数组(array)的效率要高得多,因为数组在背后存的并不是 float 对象,而是数字的机器翻译,也就是字节表述。这一点就跟 C 语言中的数组一样。再比如说,如果需要频繁对序列做先进先出的操作,deque(双端队列)的速度应该会更快。\n", 134 | "\n", 135 | "`array.tofile` 和 `fromfile` 可以将数组以二进制格式写入文件,速度要比写入文本文件快很多,文件的体积也小。\n", 136 | "\n", 137 | "> 另外一个快速序列化数字类型的方法是使用 pickle(https://docs.python.org/3/library/pickle.html)模块。pickle.dump 处理浮点数组的速度几乎跟array.tofile 一样快。不过前者可以处理几乎所有的内置数字类型,包含复数、嵌套集合,甚至用户自定义的类。前提是这些类没有什么特别复杂的实现。\n", 138 | "\n", 139 | "array 具有 `type code` 来表示数组类型:具体可见 [array 文档](https://docs.python.org/3/library/array.html)." 140 | ] 141 | }, 142 | { 143 | "cell_type": "markdown", 144 | "metadata": {}, 145 | "source": [ 146 | "### memoryview\n", 147 | "> memoryview.cast 的概念跟数组模块类似,能用不同的方式读写同一块内存数据,而且内容字节不会随意移动。" 148 | ] 149 | }, 150 | { 151 | "cell_type": "code", 152 | "execution_count": null, 153 | "metadata": {}, 154 | "outputs": [], 155 | "source": [ 156 | "import array\n", 157 | "\n", 158 | "arr = array.array('h', [1, 2, 3])\n", 159 | "memv_arr = memoryview(arr)\n", 160 | "# 把 signed short 的内存使用 char 来呈现\n", 161 | "memv_char = memv_arr.cast('B') \n", 162 | "print('Short', memv_arr.tolist())\n", 163 | "print('Char', memv_char.tolist())\n", 164 | "memv_char[1] = 2 # 更改 array 第一个数的高位字节\n", 165 | "# 0x1000000001\n", 166 | "print(memv_arr.tolist(), arr)\n", 167 | "print('-' * 10)\n", 168 | "bytestr = b'123'\n", 169 | "# bytes 是不允许更改的\n", 170 | "try:\n", 171 | " bytestr[1] = '3'\n", 172 | "except TypeError as e:\n", 173 | " print(repr(e))\n", 174 | "memv_byte = memoryview(bytestr)\n", 175 | "print('Memv_byte', memv_byte.tolist())\n", 176 | "# 同样这块内存也是只读的\n", 177 | "try:\n", 178 | " memv_byte[1] = 1\n", 179 | "except TypeError as e:\n", 180 | " print(repr(e))\n" 181 | ] 182 | }, 183 | { 184 | "cell_type": "markdown", 185 | "metadata": {}, 186 | "source": [ 187 | "### Deque\n", 188 | "`collections.deque` 是比 `list` 效率更高,且**线程安全**的双向队列实现。\n", 189 | "\n", 190 | "除了 collections 以外,以下 Python 标准库也有对队列的实现:\n", 191 | "* queue.Queue (可用于线程间通信)\n", 192 | "* multiprocessing.Queue (可用于进程间通信)\n", 193 | "* asyncio.Queue\n", 194 | "* heapq" 195 | ] 196 | } 197 | ], 198 | "metadata": { 199 | "kernelspec": { 200 | "display_name": "Python 3", 201 | "language": "python", 202 | "name": "python3" 203 | }, 204 | "language_info": { 205 | "codemirror_mode": { 206 | "name": "ipython", 207 | "version": 3 208 | }, 209 | "file_extension": ".py", 210 | "mimetype": "text/x-python", 211 | "name": "python", 212 | "nbconvert_exporter": "python", 213 | "pygments_lexer": "ipython3", 214 | "version": "3.7.0" 215 | } 216 | }, 217 | "nbformat": 4, 218 | "nbformat_minor": 2 219 | } 220 | -------------------------------------------------------------------------------- /第03章:字典和集合.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 字典和集合\n", 8 | "\n", 9 | "> 字典这个数据结构活跃在所有 Python 程序的背后,即便你的源码里并没有直接用到它。 \n", 10 | "> ——A. M. Kuchling " 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": {}, 16 | "source": [ 17 | "可散列对象需要实现 `__hash__` 和 `__eq__` 函数。 \n", 18 | "如果两个可散列对象是相等的,那么它们的散列值一定是一样的。" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": null, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "# 字典提供了很多种构造方法\n", 28 | "a = dict(one=1, two=2, three=3)\n", 29 | "b = {'one': 1, 'two': 2, 'three': 3} \n", 30 | "c = dict(zip(['one', 'two', 'three'], [1, 2, 3])) \n", 31 | "d = dict([('two', 2), ('one', 1), ('three', 3)]) \n", 32 | "e = dict({'three': 3, 'one': 1, 'two': 2})\n", 33 | "a == b == c == d == e" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "# 字典推导式\n", 43 | "r = range(5)\n", 44 | "d = {n * 2: n for n in r if n < 3}\n", 45 | "print(d)\n", 46 | "# setdefault\n", 47 | "for n in r:\n", 48 | " d.setdefault(n, 0)\n", 49 | "print(d)" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "metadata": {}, 56 | "outputs": [], 57 | "source": [ 58 | "# defaultdcit & __missing__\n", 59 | "class mydefaultdict(dict):\n", 60 | " def __init__(self, value, value_factory):\n", 61 | " super().__init__(value)\n", 62 | " self._value_factory = value_factory\n", 63 | "\n", 64 | " def __missing__(self, key):\n", 65 | " # 要避免循环调用\n", 66 | " # return self[key]\n", 67 | " self[key] = self._value_factory()\n", 68 | " return self[key]\n", 69 | "\n", 70 | "d = mydefaultdict({1:1}, list)\n", 71 | "print(d[1])\n", 72 | "print(d[2])\n", 73 | "d[3].append(1)\n", 74 | "print(d)" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "metadata": {}, 80 | "source": [ 81 | "### 字典的变种\n", 82 | "* collections.OrderedDict\n", 83 | "* collections.ChainMap (容纳多个不同的映射对象,然后在进行键查找操作时会从前到后逐一查找,直到被找到为止)\n", 84 | "* collections.Counter\n", 85 | "* colllections.UserDict (dict 的 纯 Python 实现)" 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": null, 91 | "metadata": {}, 92 | "outputs": [], 93 | "source": [ 94 | "# UserDict\n", 95 | "# 定制化字典时,尽量继承 UserDict 而不是 dict\n", 96 | "from collections import UserDict\n", 97 | "\n", 98 | "class mydict(UserDict):\n", 99 | " def __getitem__(self, key):\n", 100 | " print('Getting key', key)\n", 101 | " return super().__getitem__(key)\n", 102 | "\n", 103 | "d = mydict({1:1})\n", 104 | "print(d[1], d[2])" 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": null, 110 | "metadata": {}, 111 | "outputs": [], 112 | "source": [ 113 | "# MyppingProxyType 用于构建 Mapping 的只读实例\n", 114 | "from types import MappingProxyType\n", 115 | "\n", 116 | "d = {1: 1}\n", 117 | "d_proxy = MappingProxyType(d)\n", 118 | "print(d_proxy[1])\n", 119 | "try:\n", 120 | " d_proxy[1] = 1\n", 121 | "except Exception as e:\n", 122 | " print(repr(e))\n", 123 | "\n", 124 | "d[1] = 2\n", 125 | "print(d_proxy[1])" 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": null, 131 | "metadata": {}, 132 | "outputs": [], 133 | "source": [ 134 | "# set 的操作\n", 135 | "# 子集 & 真子集\n", 136 | "a, b = {1, 2}, {1, 2}\n", 137 | "print(a <= b, a < b)\n", 138 | "\n", 139 | "# discard\n", 140 | "a = {1, 2, 3}\n", 141 | "a.discard(3)\n", 142 | "print(a)\n", 143 | "\n", 144 | "# pop\n", 145 | "print(a.pop(), a.pop())\n", 146 | "try:\n", 147 | " a.pop()\n", 148 | "except Exception as e:\n", 149 | " print(repr(e))" 150 | ] 151 | }, 152 | { 153 | "cell_type": "markdown", 154 | "metadata": {}, 155 | "source": [ 156 | "### 集合字面量\n", 157 | "除空集之外,集合的字面量——`{1}`、`{1, 2}`,等等——看起来跟它的数学形式一模一样。**如果是空集,那么必须写成 `set()` 的形式**,否则它会变成一个 `dict`. \n", 158 | "跟 `list` 一样,字面量句法会比 `set` 构造方法要更快且更易读。" 159 | ] 160 | }, 161 | { 162 | "cell_type": "markdown", 163 | "metadata": {}, 164 | "source": [ 165 | "### 集合和字典的实现\n", 166 | "集合和字典采用散列表来实现:\n", 167 | "1. 先计算 key 的 `hash`, 根据 hash 的某几位(取决于散列表的大小)找到元素后,将该元素与 key 进行比较\n", 168 | "2. 若两元素相等,则命中\n", 169 | "3. 若两元素不等,则发生散列冲突,使用线性探测再散列法进行下一次查询。\n", 170 | "\n", 171 | "这样导致的后果:\n", 172 | "1. 可散列对象必须支持 `hash` 函数;\n", 173 | "2. 必须支持 `__eq__` 判断相等性;\n", 174 | "3. 若 `a == b`, 则必须有 `hash(a) == hash(b)`。\n", 175 | "\n", 176 | "注:所有由用户自定义的对象都是可散列的,因为他们的散列值由 id() 来获取,而且它们都是不相等的。\n", 177 | "\n", 178 | "\n", 179 | "### 字典的空间开销\n", 180 | "由于字典使用散列表实现,所以字典的空间效率低下。使用 `tuple` 代替 `dict` 可以有效降低空间消费。 \n", 181 | "不过:内存太便宜了,不到万不得已也不要开始考虑这种优化方式,**因为优化往往是可维护性的对立面**。\n", 182 | "\n", 183 | "往字典中添加键时,如果有散列表扩张的情况发生,则已有键的顺序也会发生改变。所以,**不应该在迭代字典的过程各种对字典进行更改**。" 184 | ] 185 | }, 186 | { 187 | "cell_type": "code", 188 | "execution_count": null, 189 | "metadata": {}, 190 | "outputs": [], 191 | "source": [ 192 | "# 字典中就键的顺序取决于添加顺序\n", 193 | "\n", 194 | "keys = [1, 2, 3]\n", 195 | "dict_ = {}\n", 196 | "for key in keys:\n", 197 | " dict_[key] = None\n", 198 | "\n", 199 | "for key, dict_key in zip(keys, dict_):\n", 200 | " print(key, dict_key)\n", 201 | " assert key == dict_key\n", 202 | "\n", 203 | "# 字典中键的顺序不会影响字典比较" 204 | ] 205 | } 206 | ], 207 | "metadata": { 208 | "kernelspec": { 209 | "display_name": "Python 3", 210 | "language": "python", 211 | "name": "python3" 212 | }, 213 | "language_info": { 214 | "codemirror_mode": { 215 | "name": "ipython", 216 | "version": 3 217 | }, 218 | "file_extension": ".py", 219 | "mimetype": "text/x-python", 220 | "name": "python", 221 | "nbconvert_exporter": "python", 222 | "pygments_lexer": "ipython3", 223 | "version": "3.7.0" 224 | } 225 | }, 226 | "nbformat": 4, 227 | "nbformat_minor": 2 228 | } 229 | -------------------------------------------------------------------------------- /第04章:文本与字节序列.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 文本和字节序列\n", 8 | "\n", 9 | "> 人类使用文本,计算机使用字节序列 \n", 10 | "> —— Esther Nam 和 Travis Fischer \"Character Encoding and Unicode in Python\"\n", 11 | "\n", 12 | "Python 3 明确区分了人类可读的文本字符串和原始的字节序列。 \n", 13 | "隐式地把字节序列转换成 Unicode 文本(的行为)已成过去。" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": {}, 19 | "source": [ 20 | "### 字符与编码\n", 21 | "字符的标识,及**码位**,是 0~1114111 的数字,在 Unicode 标准中用 4-6 个十六进制数字表示,如 A 为 U+0041, 高音谱号为 U+1D11E,😂 为 U+1F602. \n", 22 | "字符的具体表述取决于所用的**编码**。编码时在码位与字节序列自减转换时使用的算法。 \n", 23 | "把码位转换成字节序列的过程是**编码**,把字节序列转成码位的过程是**解码**。\n", 24 | "\n", 25 | "### 序列类型\n", 26 | "Python 内置了两种基本的二进制序列类型:不可变的 `bytes` 和可变的 `bytearray`" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": 24, 32 | "metadata": {}, 33 | "outputs": [ 34 | { 35 | "name": "stdout", 36 | "output_type": "stream", 37 | "text": [ 38 | "utf_8 b'S\\xc3\\xa3o Paulo'\n", 39 | "utf_16 b'\\xff\\xfeS\\x00\\xe3\\x00o\\x00 \\x00P\\x00a\\x00u\\x00l\\x00o\\x00'\n", 40 | "'charmap' codec can't encode character '\\xe3' in position 1: character maps to \n", 41 | "b'So Paulo'\n", 42 | "b'S?o Paulo'\n", 43 | "b'São Paulo'\n" 44 | ] 45 | } 46 | ], 47 | "source": [ 48 | "# 基本的编码\n", 49 | "content = \"São Paulo\"\n", 50 | "for codec in [\"utf_8\", \"utf_16\"]:\n", 51 | " print(codec, content.encode(codec))\n", 52 | "\n", 53 | "# UnicodeEncodeError\n", 54 | "try:\n", 55 | " content.encode('cp437')\n", 56 | "except UnicodeEncodeError as e:\n", 57 | " print(e)\n", 58 | "\n", 59 | "# 忽略无法编码的字符\n", 60 | "print(content.encode('cp437', errors='ignore'))\n", 61 | "# 把无法编码的字符替换成 ?\n", 62 | "print(content.encode('cp437', errors='replace'))\n", 63 | "# 把无法编码的字符替换成 xml 实体\n", 64 | "print(content.encode('cp437', errors='xmlcharrefreplace'))\n", 65 | "\n", 66 | "# 还可以自己设置错误处理方式\n", 67 | "# https://docs.python.org/3/library/codecs.html#codecs.register_error" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": null, 73 | "metadata": {}, 74 | "outputs": [], 75 | "source": [ 76 | "# 基本的解码\n", 77 | "# 处理 UnicodeDecodeError\n", 78 | "octets = b'Montr\\xe9al'\n", 79 | "print(octets.decode('cp1252'))\n", 80 | "print(octets.decode('iso8859_7'))\n", 81 | "print(octets.decode('koi8_r'))\n", 82 | "try:\n", 83 | " print(octets.decode('utf-8'))\n", 84 | "except UnicodeDecodeError as e:\n", 85 | " print(e)\n", 86 | "\n", 87 | "# 将错误字符替换成 � (U+FFFD)\n", 88 | "octets.decode('utf-8', errors='replace')" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": null, 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "# Python3 可以使用非 ASCII 名称\n", 98 | "São = 'Paulo'\n", 99 | "# 但是不能用 Emoji…" 100 | ] 101 | }, 102 | { 103 | "cell_type": "markdown", 104 | "metadata": {}, 105 | "source": [ 106 | "可以用 `chardet` 检测字符所使用的编码\n", 107 | "\n", 108 | "BOM:字节序标记 (byte-order mark): \n", 109 | "`\\ufffe` 为字节序标记,放在文件开头,UTF-16 用它来表示文本以大端表示(`\\xfe\\xff`)还是小端表示(`\\xff\\xfe`)。 \n", 110 | "UTF-8 编码并不需要 BOM,但是微软还是给它加了 BOM,非常烦人。\n", 111 | "\n", 112 | "### 处理文本文件\n", 113 | "处理文本文件的最佳实践是“三明治”:要尽早地把输入的字节序列解码成字符串,尽量晚地对字符串进行编码输出;在处理逻辑中只处理字符串对象,不应该去编码或解码。 \n", 114 | "除非想判断编码,否则不要再二进制模式中打开文本文件;即便如此,也应该使用 `Chardet`,而不是重新发明轮子。 \n", 115 | "常规代码只应该使用二进制模式打开二进制文件,比如图像。\n", 116 | "\n", 117 | "### 默认编码\n", 118 | "可以使用 `sys.getdefaultincoding()` 获取系统默认编码; \n", 119 | "Linux 的默认编码为 `UTF-8`,Windows 系统中不同语言设置使用的编码也不同,这导致了更多的问题。 \n", 120 | "`locale.getpreferredencoding()` 返回的编码是最重要的:这是打开文件的默认编码,也是重定向到文件的 `sys.stdout/stdin/stderr` 的默认编码。不过这个编码在某些系统中是可以改的… \n", 121 | "所以,关于编码默认值的最佳建议是:别依赖默认值。\n", 122 | "\n", 123 | "### Unicode 编码方案\n", 124 | "```python\n", 125 | "a = 'café'\n", 126 | "b = 'cafe\\u0301'\n", 127 | "print(a, b) # café café\n", 128 | "print(ascii(a), ascii(b)) # 'caf\\xe9' 'cafe\\u0301'\n", 129 | "print(len(a), len(b), a == b) # 4 5 False\n", 130 | "```\n", 131 | "\n", 132 | "在 Unicode 标准中,é 和 e\\u0301 这样的序列叫“标准等价物”,应用程序应将它视为相同的字符。但 Python 看到的是不同的码位序列,因此判断两者不相同。 \n", 133 | "我们可以用 `unicodedata.normalize` 将 Unicode 字符串规范化。有四种规范方式:NFC, NFD, NFKC, NFKD\n", 134 | "\n", 135 | "NFC 使用最少的码位构成等价的字符串,而 NFD 会把组合字符分解成基字符和单独的组合字符。 \n", 136 | "NFKC 和 NFKD 是出于兼容性考虑,在分解时会将字符替换成“兼容字符”,这种情况下会有格式损失。 \n", 137 | "兼容性方案可能会损失或曲解信息(如 \"4²\" 会被转换成 \"42\"),但可以为搜索和索引提供便利的中间表述。\n", 138 | "\n", 139 | "> 使用 NFKC 和 NFKC 规范化形式时要小心,而且只能在特殊情况中使用,例如搜索和索引,而不能用户持久存储,因为这两种转换会导致数据损失。" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": null, 145 | "metadata": {}, 146 | "outputs": [], 147 | "source": [ 148 | "from unicodedata import normalize, name\n", 149 | "# Unicode 码位\n", 150 | "a = 'café'\n", 151 | "b = 'cafe\\u0301'\n", 152 | "print(a, b)\n", 153 | "print(ascii(a), ascii(b))\n", 154 | "print(len(a), len(b), a == b)\n", 155 | "\n", 156 | "## NFC 和 NFD\n", 157 | "print(len(normalize('NFC', a)), len(normalize('NFC', b)))\n", 158 | "print(len(normalize('NFD', a)), len(normalize('NFD', b)))\n", 159 | "print(len(normalize('NFC', a)) == len(normalize('NFC', b)))\n", 160 | "\n", 161 | "print('-' * 15)\n", 162 | "# NFKC & NFKD\n", 163 | "s = '\\u00bd'\n", 164 | "l = [s, normalize('NFKC', s), normalize('NFKD', s)]\n", 165 | "print(*l)\n", 166 | "print(*map(ascii, l))\n", 167 | "micro = 'μ'\n", 168 | "l = [s, normalize('NFKC', micro)]\n", 169 | "print(*l)\n", 170 | "print(*map(ascii, l))\n", 171 | "print(*map(name, l), sep='; ')" 172 | ] 173 | }, 174 | { 175 | "cell_type": "markdown", 176 | "metadata": {}, 177 | "source": [ 178 | "### Unicode 数据库\n", 179 | "`unicodedata` 库中提供了很多关于 Unicode 的操作及判断功能,比如查看字符名称的 `name`,判断数字大小的 `numric` 等。 \n", 180 | "文档见 ." 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": 20, 186 | "metadata": {}, 187 | "outputs": [ 188 | { 189 | "name": "stdout", 190 | "output_type": "stream", 191 | "text": [ 192 | "VULGAR FRACTION ONE HALF\n", 193 | "0.5 30.0\n" 194 | ] 195 | } 196 | ], 197 | "source": [ 198 | "import unicodedata\n", 199 | "print(unicodedata.name('½'))\n", 200 | "print(unicodedata.numeric('½'), unicodedata.numeric('卅'))" 201 | ] 202 | }, 203 | { 204 | "cell_type": "code", 205 | "execution_count": 30, 206 | "metadata": {}, 207 | "outputs": [], 208 | "source": [ 209 | "# 处理鬼符:按字节序将无法处理的字节序列依序替换成 \\udc00 - \\udcff 之间的码位\n", 210 | "x = 'digits-of-π'\n", 211 | "s = x.encode('gb2312')\n", 212 | "print(s) # b'digits-of-\\xa6\\xd0'\n", 213 | "ascii_err = s.decode('ascii', 'surrogateescape')\n", 214 | "print(ascii_err) # 'digits-of-\\udca6\\udcd0'\n", 215 | "print(ascii_err.encode('ascii', 'surrogateescape')) # b'digits-of-\\xa6\\xd0'" 216 | ] 217 | } 218 | ], 219 | "metadata": { 220 | "kernelspec": { 221 | "display_name": "Python 3", 222 | "language": "python", 223 | "name": "python3" 224 | }, 225 | "language_info": { 226 | "codemirror_mode": { 227 | "name": "ipython", 228 | "version": 3 229 | }, 230 | "file_extension": ".py", 231 | "mimetype": "text/x-python", 232 | "name": "python", 233 | "nbconvert_exporter": "python", 234 | "pygments_lexer": "ipython3", 235 | "version": "3.7.0" 236 | } 237 | }, 238 | "nbformat": 4, 239 | "nbformat_minor": 2 240 | } 241 | -------------------------------------------------------------------------------- /第05章:一等函数.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 一等函数\n", 8 | "\n", 9 | "> 不管别人怎么说或怎么想,我从未觉得 Python 受到来自函数式语言的太多影响。我非常熟悉命令式语言,如 C 和 Algol 68,虽然我把函数定为一等对象,但是我并不把 Python 当作函数式编程语言。 \n", 10 | "> —— Guido van Rossum: Python 仁慈的独裁者\n", 11 | "\n", 12 | "在 Python 中,函数是一等对象。 \n", 13 | "编程语言理论家把“一等对象”定义为满足下述条件的程序实体:\n", 14 | "* 在运行时创建\n", 15 | "* 能赋值给变量或数据结构中的元素\n", 16 | "* 能作为参数传给函数\n", 17 | "* 能作为函数的返回结果" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 7, 23 | "metadata": {}, 24 | "outputs": [ 25 | { 26 | "name": "stdout", 27 | "output_type": "stream", 28 | "text": [ 29 | "['fig', 'apple', 'cherry', 'banana', 'raspberry', 'strawberry']\n", 30 | "[1, 1, 2, 6, 24, 120]\n", 31 | "5050\n", 32 | "False True\n" 33 | ] 34 | } 35 | ], 36 | "source": [ 37 | "# 高阶函数:有了一等函数(作为一等对象的函数),就可以使用函数式风格编程\n", 38 | "fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']\n", 39 | "print(list(sorted(fruits, key=len))) # 函数 len 成为了一个参数\n", 40 | "\n", 41 | "# lambda 函数 & map\n", 42 | "fact = lambda x: 1 if x == 0 else x * fact(x-1)\n", 43 | "print(list(map(fact, range(6))))\n", 44 | "\n", 45 | "# reduce\n", 46 | "from functools import reduce\n", 47 | "from operator import add\n", 48 | "print(reduce(add, range(101)))\n", 49 | "\n", 50 | "# all & any\n", 51 | "x = [0, 1]\n", 52 | "print(all(x), any(x))" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "Python 的可调用对象\n", 60 | "* 用户定义的函数:使用 `def` 或 `lambda` 创建\n", 61 | "* 内置函数:如 `len` 或 `time.strfttime`\n", 62 | "* 内置方法:如 `dict.get`(没懂这俩有什么区别…是说这个函数作为对象属性出现吗?)\n", 63 | "* 类:先调用 `__new__` 创建实例,再对实例运行 `__init__` 方法\n", 64 | "* 类的实例:如果类上定义了 `__call__` 方法,则实例可以作为函数调用\n", 65 | "* 生成器函数:调用生成器函数会返回生成器对象" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": 22, 71 | "metadata": {}, 72 | "outputs": [ 73 | { 74 | "name": "stdout", 75 | "output_type": "stream", 76 | "text": [ 77 | "1 2\n", 78 | "{'b': 1} {'d': 3}\n", 79 | "-----\n", 80 | "(a, b=1, *, c, d=3)\n", 81 | "POSITIONAL_OR_KEYWORD : a = \n", 82 | "POSITIONAL_OR_KEYWORD : b = 1\n", 83 | "KEYWORD_ONLY : c = \n", 84 | "KEYWORD_ONLY : d = 3\n", 85 | "-----\n", 86 | "\n", 87 | "\n" 88 | ] 89 | } 90 | ], 91 | "source": [ 92 | "# 获取函数中的信息\n", 93 | "# 仅限关键词参数\n", 94 | "def f(a, *, b):\n", 95 | " print(a, b)\n", 96 | "f(1, b=2)\n", 97 | "\n", 98 | "# 获取函数的默认参数\n", 99 | "# 原生的方法\n", 100 | "def f(a, b=1, *, c, d=3):\n", 101 | " pass\n", 102 | "\n", 103 | "def parse_defaults(func):\n", 104 | " code = func.__code__\n", 105 | " argcount = code.co_argcount # 2\n", 106 | " varnames = code.co_varnames # ('a', 'b', 'c', 'd')\n", 107 | " argdefaults = dict(zip(reversed(varnames[:argcount]), func.__defaults__))\n", 108 | " kwargdefaults = func.__kwdefaults__\n", 109 | " return argdefaults, kwargdefaults\n", 110 | "\n", 111 | "print(*parse_defaults(f))\n", 112 | "print('-----')\n", 113 | "# 看起来很麻烦,可以使用 inspect 模块\n", 114 | "from inspect import signature\n", 115 | "sig = signature(f)\n", 116 | "print(str(sig))\n", 117 | "for name, param in sig.parameters.items():\n", 118 | " print(param.kind, ':', name, \"=\", param.default)\n", 119 | "print('-----')\n", 120 | "# signature.bind 可以在不真正运行函数的情况下进行参数检查\n", 121 | "args = sig.bind(1, b=5, c=4)\n", 122 | "print(args)\n", 123 | "args.apply_defaults()\n", 124 | "print(args)" 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": 26, 130 | "metadata": {}, 131 | "outputs": [ 132 | { 133 | "name": "stdout", 134 | "output_type": "stream", 135 | "text": [ 136 | "\n", 137 | " : text = \n", 138 | "'int > 0' : max_len = 80\n" 139 | ] 140 | } 141 | ], 142 | "source": [ 143 | "# 函数注解\n", 144 | "def clip(text: str, max_len: 'int > 0'=80) -> str:\n", 145 | " pass\n", 146 | "\n", 147 | "from inspect import signature\n", 148 | "sig = signature(clip)\n", 149 | "print(sig.return_annotation)\n", 150 | "for param in sig.parameters.values():\n", 151 | " note = repr(param.annotation).ljust(13)\n", 152 | " print(\"{note:13} : {name} = {default}\".format(\n", 153 | " note=repr(param.annotation), name=param.name,\n", 154 | " default=param.default))" 155 | ] 156 | }, 157 | { 158 | "cell_type": "markdown", 159 | "metadata": {}, 160 | "source": [ 161 | "#### 支持函数式编程的包\n", 162 | "`operator` 里有很多函数,对应着 Python 中的内置运算符,使用它们可以避免编写很多无趣的 `lambda` 函数,如:\n", 163 | "* `add`: `lambda a, b: a + b`\n", 164 | "* `or_`: `lambda a, b: a or b`\n", 165 | "* `itemgetter`: `lambda a, b: a[b]`\n", 166 | "* `attrgetter`: `lambda a, b: getattr(a, b)`\n", 167 | "\n", 168 | "`functools` 包中提供了一些高阶函数用于函数式编程,如:`reduce` 和 `partial`。 \n", 169 | "此外,`functools.wraps` 可以保留函数的一些元信息,在编写装饰器时经常会用到。\n" 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": 23, 175 | "metadata": {}, 176 | "outputs": [ 177 | { 178 | "name": "stdout", 179 | "output_type": "stream", 180 | "text": [ 181 | "1 1 2 3 5 \n", 182 | "{'i': 5, 'j': 8}\n" 183 | ] 184 | } 185 | ], 186 | "source": [ 187 | "# Bonus: 获取闭包中的内容\n", 188 | "def fib_generator():\n", 189 | " i, j = 0, 1\n", 190 | " def f():\n", 191 | " nonlocal i, j\n", 192 | " i, j = j, i + j\n", 193 | " return i\n", 194 | " return f\n", 195 | "\n", 196 | "c = fib_generator()\n", 197 | "for _ in range(5):\n", 198 | " print(c(), end=' ')\n", 199 | "print()\n", 200 | "print(dict(zip(\n", 201 | " c.__code__.co_freevars,\n", 202 | " (x.cell_contents for x in c.__closure__))))" 203 | ] 204 | } 205 | ], 206 | "metadata": { 207 | "kernelspec": { 208 | "display_name": "Python 3", 209 | "language": "python", 210 | "name": "python3" 211 | }, 212 | "language_info": { 213 | "codemirror_mode": { 214 | "name": "ipython", 215 | "version": 3 216 | }, 217 | "file_extension": ".py", 218 | "mimetype": "text/x-python", 219 | "name": "python", 220 | "nbconvert_exporter": "python", 221 | "pygments_lexer": "ipython3", 222 | "version": "3.7.0" 223 | } 224 | }, 225 | "nbformat": 4, 226 | "nbformat_minor": 2 227 | } 228 | -------------------------------------------------------------------------------- /第06章:使用一等函数实现设计模式.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 使用一等函数实现设计模式\n", 8 | "\n", 9 | "> 符合模式并不表示做得对。\n", 10 | "> ——Ralph Johnson: 经典的《设计模式:可复用面向对象软件的基础》的作者之一\n", 11 | "\n", 12 | "本章将对现有的一些设计模式进行简化,从而减少样板代码。" 13 | ] 14 | }, 15 | { 16 | "cell_type": "markdown", 17 | "metadata": {}, 18 | "source": [ 19 | "## 策略模式\n", 20 | "实现策略模式,可以依赖 `abc.ABC` 和 `abc.abstractmethod` 来构建抽象基类。 \n", 21 | "但为了实现“策略”,并不一定要针对每种策略来编写子类,如果需求简单,编写函数就好了。 \n", 22 | "我们可以通过 `globals()` 函数来发现所有策略函数,并遍历并应用策略函数来找出最优策略。" 23 | ] 24 | }, 25 | { 26 | "cell_type": "markdown", 27 | "metadata": {}, 28 | "source": [ 29 | "## 命令模式\n", 30 | "“命令模式”的目的是解耦操作调用的对象(调用者)和提供实现的对象(接受者)。\n", 31 | "\n", 32 | "在 Python 中,我们可以通过定义 `Command` 基类来规范命令调用协议;通过在类上定义 `__call__` 函数,还可以使对象支持直接调用。\n", 33 | "\n", 34 | "```python\n", 35 | "import abc\n", 36 | "\n", 37 | "class BaseCommand(ABC):\n", 38 | " def execute(self, *args, **kwargs):\n", 39 | " raise NotImplemented\n", 40 | "```" 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "metadata": {}, 46 | "source": [ 47 | "> 事实证明,在 Gamma 等人合著的那本书中,尽管大部分使用 C++ 代码说明(少数使用 Smalltalk),但是 23 个“经典的”设计模式都能很好地在“经典的”Java 中运用。然而,这并不意味着所有模式都能一成不变地在任何语言中运用。" 48 | ] 49 | } 50 | ], 51 | "metadata": { 52 | "kernelspec": { 53 | "display_name": "Python 3", 54 | "language": "python", 55 | "name": "python3" 56 | }, 57 | "language_info": { 58 | "codemirror_mode": { 59 | "name": "ipython", 60 | "version": 3 61 | }, 62 | "file_extension": ".py", 63 | "mimetype": "text/x-python", 64 | "name": "python", 65 | "nbconvert_exporter": "python", 66 | "pygments_lexer": "ipython3", 67 | "version": "3.6.3" 68 | } 69 | }, 70 | "nbformat": 4, 71 | "nbformat_minor": 2 72 | } 73 | -------------------------------------------------------------------------------- /第08章:对象引用、可变性和垃圾回收.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 对象引用、可变性和垃圾回收\n", 8 | "\n", 9 | "~~这章的前言实在是太长了…~~\n", 10 | "\n", 11 | "Python 使用引用式变量:变量和变量名是两个不同的东西。\n", 12 | "\n", 13 | "在 Python 中,变量不是一个存储数据的盒子,而是一个针对盒子的标注。同时,一个盒子上可以有很多标注,也可以一个都没有。" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 3, 19 | "metadata": {}, 20 | "outputs": [ 21 | { 22 | "name": "stdout", 23 | "output_type": "stream", 24 | "text": [ 25 | "[1, 2, 3, 4]\n", 26 | "ID 88908560\n", 27 | "Test <__main__.Test object at 0x054CA310> 88908560\n" 28 | ] 29 | } 30 | ], 31 | "source": [ 32 | "a = [1, 2, 3]\n", 33 | "b = a\n", 34 | "a.append(4)\n", 35 | "print(b)\n", 36 | "\n", 37 | "# 对象会在赋值之前创建\n", 38 | "class Test:\n", 39 | " def __init__(self):\n", 40 | " print('ID', id(self)) # 在把 self 分配给 t 之前,self 就已经有了自己的 ID\n", 41 | "\n", 42 | "t = Test()\n", 43 | "print('Test', t, id(t))" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": 6, 49 | "metadata": {}, 50 | "outputs": [], 51 | "source": [ 52 | "# 分清“==”和“is”\n", 53 | "# a is b 等同于 id(a) == id(b)\n", 54 | "a = {'a': 1}\n", 55 | "b = a\n", 56 | "assert a is b\n", 57 | "assert id(a) == id(b)\n", 58 | "\n", 59 | "c = {'a': 1}\n", 60 | "assert a == c\n", 61 | "assert not a is c\n", 62 | "assert id(a) != id(c)" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "metadata": {}, 68 | "source": [ 69 | "在 CPython 中,id() 返回对象的内存地址,但在其他 Python 解释器中可能是别的值。关键是,ID 一定是唯一的数值标注,而且在对象的生命周期中绝不会变。 \n", 70 | "编程中,我们很少会使用 `id()` 函数,标识最常使用 `is` 运算符检查相同性,而不是直接比较 ID." 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": 7, 76 | "metadata": {}, 77 | "outputs": [], 78 | "source": [ 79 | "# 与单例进行比较时,应该使用 is\n", 80 | "a = None\n", 81 | "assert a is None\n", 82 | "b = ...\n", 83 | "assert b is ...\n", 84 | "\n", 85 | "# a == b 是语法糖,等同于 a.__eq__(b)" 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": 11, 91 | "metadata": {}, 92 | "outputs": [ 93 | { 94 | "name": "stdout", 95 | "output_type": "stream", 96 | "text": [ 97 | "(1, 2, [30, 40, 50])\n" 98 | ] 99 | } 100 | ], 101 | "source": [ 102 | "# 元组的相对不可变性\n", 103 | "t1 = (1, 2, [30, 40])\n", 104 | "t2 = (1, 2, [30, 40])\n", 105 | "assert t1 == t2\n", 106 | "\n", 107 | "id1 = id(t1[-1])\n", 108 | "t1[-1].append(50)\n", 109 | "print(t1)\n", 110 | "\n", 111 | "id2 = id(t1[-1])\n", 112 | "assert id1 == id2 # t1 的最后一个元素的标识没变,但是值变了\n", 113 | "assert t1 != t2" 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": 13, 119 | "metadata": {}, 120 | "outputs": [ 121 | { 122 | "name": "stdout", 123 | "output_type": "stream", 124 | "text": [ 125 | "[1, [2, 3, 4, 5], (4, 5, 6)] [1, [2, 3, 4, 5], (4, 5, 6, 7, 8)]\n" 126 | ] 127 | } 128 | ], 129 | "source": [ 130 | "# 浅复制\n", 131 | "# Python 对列表等进行复制时,只会复制容器,而不会复制容器内的内容\n", 132 | "a = [1, [2, 3], (4, 5, 6)]\n", 133 | "b = list(a)\n", 134 | "assert a == b\n", 135 | "assert a is not b # b 是一个新对象\n", 136 | "assert a[1] is b[1] # 但两个列表中的元素是同一个\n", 137 | "\n", 138 | "a[1] += [4, 5] # a[1] = a[1] + [4, 5], list 就地执行操作后返回对象本身的引用\n", 139 | "b[2] += (7, 8) # b[2] = b[2] + (7, 8), tuple 在执行操作后生成一个新对象并返回它的引用\n", 140 | "print(a, b)" 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": 15, 146 | "metadata": {}, 147 | "outputs": [ 148 | { 149 | "name": "stdout", 150 | "output_type": "stream", 151 | "text": [ 152 | "[1, [2, 3], (4, 5, 6)] [1, [2, 3, 4, 5], (4, 5, 6)]\n" 153 | ] 154 | } 155 | ], 156 | "source": [ 157 | "# 深复制\n", 158 | "from copy import deepcopy\n", 159 | "a = [1, [2, 3], (4, 5, 6)]\n", 160 | "b = deepcopy(a)\n", 161 | "assert a == b\n", 162 | "assert a[1] is not b[1] # 不单复制了容器,还复制了容器中的值\n", 163 | "\n", 164 | "b[1].extend([4, 5])\n", 165 | "print(a, b)\n", 166 | "assert a != b" 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": 16, 172 | "metadata": {}, 173 | "outputs": [], 174 | "source": [ 175 | "# Python 的传参方式:共享传参(call by sharing),只会传递引用\n", 176 | "def f(a, b):\n", 177 | " a += b\n", 178 | " return a\n", 179 | "\n", 180 | "a, b = [1, 2], [3, 4]\n", 181 | "c = f(a, b)\n", 182 | "assert a == c == [1, 2, 3, 4]\n", 183 | "assert a is c" 184 | ] 185 | }, 186 | { 187 | "cell_type": "code", 188 | "execution_count": 19, 189 | "metadata": {}, 190 | "outputs": [ 191 | { 192 | "name": "stdout", 193 | "output_type": "stream", 194 | "text": [ 195 | "[1, 2] [1, 2] [3]\n", 196 | "[3]\n" 197 | ] 198 | } 199 | ], 200 | "source": [ 201 | "# 共享传参导致的问题\n", 202 | "# 可变参数作为函数默认值 & 跨作用域引用导致共享变量更改\n", 203 | "class Container:\n", 204 | " def __init__(self, initial=[]):\n", 205 | " self.values = initial\n", 206 | " \n", 207 | " def add(self, value):\n", 208 | " self.values.append(value)\n", 209 | "\n", 210 | "a = Container()\n", 211 | "b = Container()\n", 212 | "l = []\n", 213 | "c = Container(l)\n", 214 | "a.add(1)\n", 215 | "b.add(2)\n", 216 | "c.add(3)\n", 217 | "print(a.values, b.values, c.values)\n", 218 | "assert a.values is b.values # a.values 和 b.values 共享同一个变量(init 函数中的默认值)\n", 219 | "print(l) # l 和 c.values 共享同一个变量,c.values 更改后,l 也会更改\n", 220 | "assert c.values is l" 221 | ] 222 | }, 223 | { 224 | "cell_type": "markdown", 225 | "metadata": {}, 226 | "source": [ 227 | "## del 和垃圾回收\n", 228 | "> 对象绝不会自行销毁;然而,无法得到对象时,可能会被当做垃圾回收。\n", 229 | "\n", 230 | "在 CPython 中,垃圾回收使用的主要算法时引用计数。当引用计数归零时,对象立即就被销毁。 \n", 231 | "销毁时,CPython 会先调用对象的 `__del__` 方法,然后释放分配给对象的内存。 \n", 232 | "CPython 2.0 增加了分代垃圾回收机制,用于检测循环引用中涉及的对象组。" 233 | ] 234 | }, 235 | { 236 | "cell_type": "code", 237 | "execution_count": 20, 238 | "metadata": {}, 239 | "outputs": [ 240 | { 241 | "name": "stdout", 242 | "output_type": "stream", 243 | "text": [ 244 | "True\n", 245 | "True\n", 246 | "Bye\n", 247 | "False\n" 248 | ] 249 | } 250 | ], 251 | "source": [ 252 | "# 监测引用计数垃圾回收\n", 253 | "import weakref\n", 254 | "s1 = s2 = {1}\n", 255 | "ender = weakref.finalize(s1, lambda: print('Bye'))\n", 256 | "print(ender.alive)\n", 257 | "del s1 # 删除 s1 的引用\n", 258 | "print(ender.alive) # 对象 {1} 的引用还在(s2)\n", 259 | "s2 = {2}\n", 260 | "print(ender.alive) # 无法引用到 {1} 对象,于是它被垃圾回收" 261 | ] 262 | }, 263 | { 264 | "cell_type": "markdown", 265 | "metadata": {}, 266 | "source": [ 267 | "## 弱引用\n", 268 | "使用 `weakref.ref` 可以生成一个对象的引用,但不会增加它的引用计数\n", 269 | "\n", 270 | "`weakref.ref` 类实际上是低层接口,多数程序最好使用 `weakref` 集合和 `finalize`.\n", 271 | "\n", 272 | "`weakref` 提供的弱引用集合有 `WeakKeyDictionary`, `WeakValueDictionary`, `WeakSet`. \n", 273 | "它们的用途可以从名字中得出。\n", 274 | "\n", 275 | "弱引用的局限:并不是所有对象都可以创建弱引用,比如 `list`, `dict`, `str` 实例。 \n", 276 | "但是,某些自定义类都可以创建弱引用(比如基于 `list` 生成的子类)。 \n", 277 | "`int` 和 `tuple` 实例及它们的子类实例都不能成为弱引用的目标。" 278 | ] 279 | }, 280 | { 281 | "cell_type": "code", 282 | "execution_count": 24, 283 | "metadata": {}, 284 | "outputs": [ 285 | { 286 | "name": "stdout", 287 | "output_type": "stream", 288 | "text": [ 289 | " {1}\n", 290 | " None\n" 291 | ] 292 | } 293 | ], 294 | "source": [ 295 | "import weakref\n", 296 | "\n", 297 | "s1 = {1}\n", 298 | "ref = weakref.ref(s1)\n", 299 | "print(ref, ref())\n", 300 | "del s1\n", 301 | "print(ref, ref())" 302 | ] 303 | }, 304 | { 305 | "cell_type": "code", 306 | "execution_count": 29, 307 | "metadata": {}, 308 | "outputs": [ 309 | { 310 | "name": "stdout", 311 | "output_type": "stream", 312 | "text": [ 313 | "[('s1', {1}), ('s2', {2})]\n", 314 | "[('s1', {1})]\n" 315 | ] 316 | } 317 | ], 318 | "source": [ 319 | "# WeakValueDictionary 实例\n", 320 | "from weakref import WeakValueDictionary\n", 321 | "\n", 322 | "weak_dict = WeakValueDictionary()\n", 323 | "s1, s2 = {1}, {2}\n", 324 | "weak_dict.update({\n", 325 | " 's1': s1,\n", 326 | " 's2': s2\n", 327 | "})\n", 328 | "print(list(weak_dict.items()))\n", 329 | "del s2\n", 330 | "print(list(weak_dict.items()))" 331 | ] 332 | }, 333 | { 334 | "cell_type": "markdown", 335 | "metadata": {}, 336 | "source": [ 337 | "## 一些杂事\n", 338 | "如果不可变集合(如 `tuple` 和 `frozenset`)中保存的是可变元素的引用,那么可变元素的值发生变化后,不可变集合也会发生改变。 \n", 339 | "这里指的是 `hash` 和 `==`的结果,即使集合中的对象更改,该对象在集合中存储的引用也是不会变的。\n", 340 | "\n", 341 | "`tuple()` 的参数如果是一个元组,则得到的是同一个对象。对元组使用 `[:]` 切片操作也不会生成新对象。 \n", 342 | "`str`, `bytes` 和 `frozenset` 实例也有这种行为。 \n", 343 | "`fs.copy()` 返回的是它本身(喵喵喵?)。\n", 344 | "\n", 345 | "字符串字面量可能会产生**驻留**现象:两个相等的字符串共享同一个字符串对象。 \n", 346 | "`int` 对象中,在 [-5, 256] 之间的整型实例也被提前创建,所有指向这些数字的引用均会共享对象。\n", 347 | "\n", 348 | "自定义对象中,如果没有实现 `__eq__` 方法,则进行 `==` 判断时会比较它们的 ID. \n", 349 | "这种后备机制认为用户定义的类的各个实例是不同的。" 350 | ] 351 | }, 352 | { 353 | "cell_type": "code", 354 | "execution_count": 32, 355 | "metadata": {}, 356 | "outputs": [], 357 | "source": [ 358 | "a = \"hahaha\"\n", 359 | "b = \"hahaha\"\n", 360 | "assert a is b\n", 361 | "\n", 362 | "a = 66\n", 363 | "b = 66\n", 364 | "assert a is b\n", 365 | "a = 257\n", 366 | "b = 257\n", 367 | "assert a is not b\n", 368 | "\n", 369 | "class T:\n", 370 | " pass\n", 371 | "\n", 372 | "a = T()\n", 373 | "b = a\n", 374 | "c = T()\n", 375 | "assert a is b\n", 376 | "assert a is not c" 377 | ] 378 | }, 379 | { 380 | "cell_type": "markdown", 381 | "metadata": {}, 382 | "source": [ 383 | "## 延伸阅读\n", 384 | "[Python Garbage Collector Implementations: CPython, PyPy and Gas](https://thp.io/2012/python-gc/python_gc_final_2012-01-22.pdf)" 385 | ] 386 | } 387 | ], 388 | "metadata": { 389 | "kernelspec": { 390 | "display_name": "Python 3", 391 | "language": "python", 392 | "name": "python3" 393 | }, 394 | "language_info": { 395 | "codemirror_mode": { 396 | "name": "ipython", 397 | "version": 3 398 | }, 399 | "file_extension": ".py", 400 | "mimetype": "text/x-python", 401 | "name": "python", 402 | "nbconvert_exporter": "python", 403 | "pygments_lexer": "ipython3", 404 | "version": "3.6.3" 405 | } 406 | }, 407 | "nbformat": 4, 408 | "nbformat_minor": 2 409 | } 410 | -------------------------------------------------------------------------------- /第09章:符合Python风格的对象.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 符合 Python 风格的对象\n", 8 | "> 绝对不要使用两个前导下划线,这是很烦人的自私行为。 \n", 9 | "> ——Ian Bicking \n", 10 | "> pip, virtualenv 和 Paste 等项目的创建者\n", 11 | "\n", 12 | "得益于 Python 数据模型,自定义类型的行为可以像内置类型那样自然。 \n", 13 | "实现如此自然的行为,靠的不是继承,而是**鸭子类型**(duck typing):我们只需按照预定行为实现对象所需的方法即可。\n", 14 | "\n", 15 | "当然,这些方法不是必须要实现的,我们需要哪些功能,再去实现它就好。\n", 16 | "\n", 17 | "书中的 `Vector` 类完整实现可见[官方 Repo](https://github.com/fluentpython/example-code/blob/master/09-pythonic-obj/vector2d_v3.py)." 18 | ] 19 | }, 20 | { 21 | "cell_type": "markdown", 22 | "metadata": {}, 23 | "source": [ 24 | "## 对象表现形式\n", 25 | "Python 中,有两种获取对象的字符串表示形式的标准方式:\n", 26 | "* `repr()`:以便于开发者理解的方式返回对象的字符串表示形式\n", 27 | "* `str()`:以便于用户理解的方式返回对象的字符串表示形式\n", 28 | "\n", 29 | "实现 `__repr__` 和 `__str__` 两个特殊方法,可以分别为 `repr` 和 `str` 提供支持。" 30 | ] 31 | }, 32 | { 33 | "cell_type": "markdown", 34 | "metadata": {}, 35 | "source": [ 36 | "## classmethod & staticmethod\n", 37 | "`classmethod`: 定义操作**类**,而不是操作**实例**的方法。 \n", 38 | "`classmethod` 最常见的用途是定义备选构造方法,比如 `datetime.fromordinal` 和 `datetime.fromtimestamp`.\n", 39 | "\n", 40 | "`staticmethod` 也会改变方法的调用方式,但方法的第一个参数不是特殊的值(`self` 或 `cls`)。 \n", 41 | "`staticmethod` 可以把一些静态函数定义在类中而不是模块中,但抛开 `staticmethod`,我们也可以用其它方法来实现相同的功能。" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 1, 47 | "metadata": {}, 48 | "outputs": [ 49 | { 50 | "name": "stdout", 51 | "output_type": "stream", 52 | "text": [ 53 | "(, 1) (1,)\n", 54 | "(, 1) (1,) (<__main__.Demo object at 0x05E719B0>, 1)\n" 55 | ] 56 | } 57 | ], 58 | "source": [ 59 | "class Demo:\n", 60 | " def omethod(*args):\n", 61 | " # 第一个参数是 Demo 对象\n", 62 | " return args\n", 63 | " @classmethod\n", 64 | " def cmethod(*args):\n", 65 | " # 第一个参数是 Demo 类\n", 66 | " return args\n", 67 | " @staticmethod\n", 68 | " def smethod(*args):\n", 69 | " # 第一个参数不是固定的,由调用者传入\n", 70 | " return args\n", 71 | "\n", 72 | "print(Demo.cmethod(1), Demo.smethod(1))\n", 73 | "demo = Demo()\n", 74 | "print(demo.cmethod(1), demo.smethod(1), demo.omethod(1))" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "metadata": {}, 80 | "source": [ 81 | "## 字符串模板化\n", 82 | "`__format__` 实现了 `format` 方法的接口,它的参数为 `format` 格式声明符,格式声明符的表示法叫做[格式规范微语言](https://docs.python.org/3/library/string.html#formatspec)。 \n", 83 | "`str.format` 的声明符表示法和格式规范微语言类似,称作[格式字符串句法](https://docs.python.org/3/library/string.html#formatstrings)。" 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": 2, 89 | "metadata": {}, 90 | "outputs": [ 91 | { 92 | "name": "stdout", 93 | "output_type": "stream", 94 | "text": [ 95 | "15 15.00 F\n" 96 | ] 97 | } 98 | ], 99 | "source": [ 100 | "# format\n", 101 | "num = 15\n", 102 | "print(format(num, '2d'), format(num, '.2f'), format(num, 'X'))" 103 | ] 104 | }, 105 | { 106 | "cell_type": "markdown", 107 | "metadata": {}, 108 | "source": [ 109 | "## 对象散列化\n", 110 | "只有可散列的对象,才可以作为 `dict` 的 key,或者被加入 `set`. \n", 111 | "要想创建可散列的类型,只需要实现 `__hash__` 和 `__eq__` 即可。 \n", 112 | "有一个要求:如果 `a == b`,那么 `hash(a) == hash(b)`.\n", 113 | "\n", 114 | "文中推荐的 hash 实现方法是使用位运算异或各个分量的散列值:\n", 115 | "```python\n", 116 | "class Vector2d:\n", 117 | " # 其余部分省略\n", 118 | " def __hash__(self):\n", 119 | " return hash(self.x) ^ hash(self.y)\n", 120 | "```\n", 121 | "\n", 122 | "而最新的文章中,推荐把各个分量组成一个 `tuple`,然后对其进行散列:\n", 123 | "```python\n", 124 | "class Vector2d:\n", 125 | " def __hash__(self):\n", 126 | " return hash((self.x, self.y))\n", 127 | "```" 128 | ] 129 | }, 130 | { 131 | "cell_type": "markdown", 132 | "metadata": {}, 133 | "source": [ 134 | "## Python 的私有属性和“受保护的”属性\n", 135 | "Python 的“双下前导”变量:如果以 `__a` 的形式(双前导下划线,尾部最多有一个下划线)命名一个实例属性,Python 会在 `__dict__` 中将该属性进行“名称改写”为 `_Klass__a`,以防止外部对对象内部属性的直接访问。 \n", 136 | "这样可以避免意外访问,但**不能防止故意犯错**。\n", 137 | "\n", 138 | "有一种观点认为不应该使用“名称改写”特性,对于私有方法来说,可以使用前导单下划线 `_x` 来标注,而不应使用双下划线。\n", 139 | "\n", 140 | "此外:在 `from mymod import *` 中,任何使用下划线前缀(无论单双)的变量,都不会被导入。" 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": 1, 146 | "metadata": {}, 147 | "outputs": [ 148 | { 149 | "name": "stdout", 150 | "output_type": "stream", 151 | "text": [ 152 | "{'_Vector2d__x': 1, '_Vector2d__y': 2}\n", 153 | "1\n" 154 | ] 155 | } 156 | ], 157 | "source": [ 158 | "class Vector2d:\n", 159 | " def __init__(self, x, y):\n", 160 | " self.__x = x\n", 161 | " self.__y = y\n", 162 | " \n", 163 | "vector = Vector2d(1, 2)\n", 164 | "print(vector.__dict__)\n", 165 | "print(vector._Vector2d__x)" 166 | ] 167 | }, 168 | { 169 | "cell_type": "markdown", 170 | "metadata": {}, 171 | "source": [ 172 | "## 使用 __slot__ 类属性节省空间\n", 173 | "默认情况下,Python 在各个实例的 `__dict__` 字典中存储实例属性。但 Python 字典使用散列表实现,会消耗大量内存。 \n", 174 | "如果需要处理很多个属性**有限且相同**的实例,可以通过定义 `__slots__` 类属性,将实例用元组存储,以节省内存。\n", 175 | "\n", 176 | "```python\n", 177 | "class Vector2d:\n", 178 | " __slots__ = ('__x', '__y')\n", 179 | "```\n", 180 | "\n", 181 | "`__slots__` 的目的是优化内存占用,而不是防止别人在实例中添加属性。_所以一般也没有什么使用的必要。_\n", 182 | "\n", 183 | "使用 `__slots__` 时需要注意的地方:\n", 184 | "* 子类不会继承父类的 `__slots_`\n", 185 | "* 实例只能拥有 `__slots__` 中所列出的属性\n", 186 | "* 如果不把 `\"__weakref__\"` 加入 `__slots__`,实例就不能作为弱引用的目标。" 187 | ] 188 | }, 189 | { 190 | "cell_type": "code", 191 | "execution_count": 7, 192 | "metadata": {}, 193 | "outputs": [ 194 | { 195 | "name": "stdout", 196 | "output_type": "stream", 197 | "text": [ 198 | "b'd\\x00\\x00\\x00\\x00\\x00\\x00\\xf0?\\x00\\x00\\x00\\x00\\x00\\x00\\x00@'\n", 199 | "(1, 2) (1.0, 2.0)\n" 200 | ] 201 | } 202 | ], 203 | "source": [ 204 | "# 对象二进制化\n", 205 | "from array import array\n", 206 | "import math\n", 207 | "\n", 208 | "class Vector2d:\n", 209 | " typecode = 'd'\n", 210 | "\n", 211 | " def __init__(self, x, y):\n", 212 | " self.x = x\n", 213 | " self.y = y\n", 214 | " \n", 215 | " def __str__(self):\n", 216 | " return str(tuple(self))\n", 217 | "\n", 218 | " def __iter__(self):\n", 219 | " yield from (self.x, self.y)\n", 220 | "\n", 221 | " def __bytes__(self):\n", 222 | " return (bytes([ord(self.typecode)]) +\n", 223 | " bytes(array(self.typecode, self)))\n", 224 | " \n", 225 | " def __eq__(self, other):\n", 226 | " return tuple(self) == tuple(other)\n", 227 | "\n", 228 | " @classmethod\n", 229 | " def frombytes(cls, octets):\n", 230 | " typecode = chr(octets[0])\n", 231 | " memv = memoryview(octets[1:]).cast(typecode)\n", 232 | " return cls(*memv)\n", 233 | "\n", 234 | "vector = Vector2d(1, 2)\n", 235 | "v_bytes = bytes(vector)\n", 236 | "vector2 = Vector2d.frombytes(v_bytes)\n", 237 | "print(v_bytes)\n", 238 | "print(vector, vector2)\n", 239 | "assert vector == vector2" 240 | ] 241 | } 242 | ], 243 | "metadata": { 244 | "kernelspec": { 245 | "display_name": "Python 3", 246 | "language": "python", 247 | "name": "python3" 248 | }, 249 | "language_info": { 250 | "codemirror_mode": { 251 | "name": "ipython", 252 | "version": 3 253 | }, 254 | "file_extension": ".py", 255 | "mimetype": "text/x-python", 256 | "name": "python", 257 | "nbconvert_exporter": "python", 258 | "pygments_lexer": "ipython3", 259 | "version": "3.7.0" 260 | } 261 | }, 262 | "nbformat": 4, 263 | "nbformat_minor": 2 264 | } 265 | -------------------------------------------------------------------------------- /第10章:序列的修改、散列和切片.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 序列的修改、散列和切片\n", 8 | "> 不要检查它**是不是**鸭子、它的**叫声**像不像鸭子、它的**走路姿势**像不像鸭子,等等。具体检查什么取决于你想用语言的哪些行为。(comp.lang.python, 2000 年 7 月 26 日) \n", 9 | "> ——Alex Martelli\n", 10 | "\n", 11 | "本章在 `Vector2d` 基础上进行改进以支持多维向量。不过我不想写那么多 `Vector` 代码了,所以我会尝试对里面讲到的知识进行一些抽象。\n", 12 | "\n", 13 | "当然,如果有兴趣也可以研究一下书中实现的[多维向量代码](https://github.com/fluentpython/example-code/tree/master/10-seq-hacking)。" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": {}, 19 | "source": [ 20 | "## Python 序列协议\n", 21 | "鸭子类型(duck typing):“如果一个东西长的像鸭子、叫声像鸭子、走路像鸭子,那我们可以认为它就是鸭子。”\n", 22 | "\n", 23 | "Python 中,如果我们在类上实现了 `__len__` 和 `__getitem__` 接口,我们就可以把它用在任何期待序列的场景中。" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 11, 29 | "metadata": {}, 30 | "outputs": [ 31 | { 32 | "name": "stdout", 33 | "output_type": "stream", 34 | "text": [ 35 | "3\n", 36 | "[3, 4] [1, 2, 3, 4, 5] [1, 3, 5] [4, 5] [1, 2, 3, 4, 5]\n", 37 | "[1, 2, 3, 4] [5, 4, 3, 2]\n" 38 | ] 39 | } 40 | ], 41 | "source": [ 42 | "# 序列协议中的切片\n", 43 | "class SomeSeq:\n", 44 | " def __init__(self, seq):\n", 45 | " self._seq = list(seq)\n", 46 | " \n", 47 | " def __len__(self):\n", 48 | " return len(self._seq)\n", 49 | "\n", 50 | " def __getitem__(self, index):\n", 51 | " if isinstance(index, slice):\n", 52 | " # 专门对 slice 做一个自己的实现\n", 53 | " start, stop = index.start, index.stop\n", 54 | " step = index.step\n", 55 | " \n", 56 | " if start is None:\n", 57 | " start = 0\n", 58 | " elif start < 0:\n", 59 | " start = len(self) + start\n", 60 | " else:\n", 61 | " start = min(start, len(self))\n", 62 | " if stop is None:\n", 63 | " stop = len(self)\n", 64 | " elif stop < 0:\n", 65 | " stop = len(self) + stop\n", 66 | " else:\n", 67 | " stop = min(stop, len(self))\n", 68 | " if step is None:\n", 69 | " step = 1\n", 70 | " elif step == 0:\n", 71 | " raise ValueError(\"slice step cannot be zero\")\n", 72 | "\n", 73 | " # 以上的复杂逻辑可以直接使用 slice 的接口\n", 74 | " # start, stop, step = index.indices(len(self))\n", 75 | " index_range = range(start, stop, step)\n", 76 | " return [self._seq[i] for i in index_range]\n", 77 | " else:\n", 78 | " return self._seq[index]\n", 79 | "\n", 80 | " \n", 81 | "seq = SomeSeq([1, 2, 3, 4, 5])\n", 82 | "print(seq[2])\n", 83 | "print(seq[2:4], seq[:5], seq[:5:2], seq[3:], seq[:200])\n", 84 | "print(seq[:-1], seq[-1:-5:-1])" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": 12, 90 | "metadata": { 91 | "scrolled": false 92 | }, 93 | "outputs": [ 94 | { 95 | "name": "stdout", 96 | "output_type": "stream", 97 | "text": [ 98 | "(1, 2, 3)\n", 99 | "(slice(None, 5, None), slice(2, 5, 2), slice(-1, 5, 3))\n" 100 | ] 101 | } 102 | ], 103 | "source": [ 104 | "# __getitem__ 的参数不一定是单个值或者 slice,还有可能是 tuple\n", 105 | "class SomeSeq:\n", 106 | " def __init__(self, seq):\n", 107 | " self._seq = list(seq)\n", 108 | " \n", 109 | " def __len__(self):\n", 110 | " return len(self._seq)\n", 111 | "\n", 112 | " def __getitem__(self, item):\n", 113 | " return item\n", 114 | "\n", 115 | "seq = SomeSeq([1, 2, 3, 4, 5])\n", 116 | "print(seq[1, 2, 3])\n", 117 | "print(seq[:5, 2:5:2, -1:5:3])" 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": 14, 123 | "metadata": {}, 124 | "outputs": [ 125 | { 126 | "name": "stdout", 127 | "output_type": "stream", 128 | "text": [ 129 | "1 1\n", 130 | "2 2\n", 131 | "3 3\n", 132 | "---\n", 133 | "0 1\n", 134 | "1 2\n", 135 | "2 3\n", 136 | "---\n", 137 | "[(1, 2, 3), (4, 5, 6), (7, 8, 9)]\n" 138 | ] 139 | } 140 | ], 141 | "source": [ 142 | "# zip 和 enumerate: 知道这两个方法可以简化一些场景\n", 143 | "l1, l2 = [1, 2, 3], [1, 2, 3, 4, 5]\n", 144 | "for n1, n2 in zip(l1, l2):\n", 145 | " print(n1, n2)\n", 146 | "\n", 147 | "print('---')\n", 148 | "# 不要这么写\n", 149 | "# for index in range(len(l1)):\n", 150 | "# print(index, l1[index])\n", 151 | "for index, obj in enumerate(l1):\n", 152 | " print(index, obj)\n", 153 | "\n", 154 | "print('---')\n", 155 | "# list 分组的快捷操作\n", 156 | "# 注意:只用于序列长度是分组的倍数的场景,否则最后一组会丢失\n", 157 | "l = [1,2,3,4,5,6,7,8,9]\n", 158 | "print(list(zip(*[iter(l)] * 3)))" 159 | ] 160 | } 161 | ], 162 | "metadata": { 163 | "kernelspec": { 164 | "display_name": "Python 3", 165 | "language": "python", 166 | "name": "python3" 167 | }, 168 | "language_info": { 169 | "codemirror_mode": { 170 | "name": "ipython", 171 | "version": 3 172 | }, 173 | "file_extension": ".py", 174 | "mimetype": "text/x-python", 175 | "name": "python", 176 | "nbconvert_exporter": "python", 177 | "pygments_lexer": "ipython3", 178 | "version": "3.7.0" 179 | } 180 | }, 181 | "nbformat": 4, 182 | "nbformat_minor": 2 183 | } 184 | -------------------------------------------------------------------------------- /第11章:接口:从协议到抽象基类.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 接口:从协议到抽象基类\n", 8 | "> 抽象类表示接口。 \n", 9 | "> ——Bjarne Stroustrup, C++ 之父\n", 10 | "\n", 11 | "本章讨论的话题是接口:从**鸭子类型**的代表特征动态协议,到使接口更明确、能验证实现是否符合规定的抽象基类(Abstract Base Class, ABC)。\n", 12 | "\n", 13 | "> 接口的定义:对象公开方法的子集,让对象在系统中扮演特定的角色。 \n", 14 | "> 协议是接口,但不是正式的(只由文档和约定定义),因此协议不能像正式接口那样施加限制。 \n", 15 | "> 允许一个类上只实现部分接口。" 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "metadata": {}, 21 | "source": [ 22 | "## 抽象基类(abc)\n", 23 | "抽象基类是一个非常实用的功能,可以使用抽象基类来检测某个类是否实现了某种协议,而这个类并不需要继承自这个抽象类。 \n", 24 | "[`collections.abc`](https://docs.python.org/3/library/collections.abc.html) 和 [`numbers`](https://docs.python.org/3/library/numbers.html) 模块中提供了许多常用的抽象基类以用于这种检测。\n", 25 | "\n", 26 | "有了这个功能,我们在自己实现函数时,就不需要非常关心外面传进来的参数的**具体类型**(`isinstance(param, list)`),只需要关注这个参数是否支持我们**需要的协议**(`isinstance(param, abc.Sequence`)以保障正常使用就可以了。\n", 27 | "\n", 28 | "但是注意:从 Python 简洁性考虑,最好不要自己创建新的抽象基类,而应尽量考虑使用现有的抽象基类。" 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": 4, 34 | "metadata": {}, 35 | "outputs": [], 36 | "source": [ 37 | "# 抽象基类\n", 38 | "from collections import abc\n", 39 | "\n", 40 | "\n", 41 | "class A:\n", 42 | " pass\n", 43 | "\n", 44 | "class B:\n", 45 | " def __len__(self):\n", 46 | " return 0\n", 47 | "\n", 48 | "assert not isinstance(A(), abc.Sized)\n", 49 | "assert isinstance(B(), abc.Sized)\n", 50 | "assert abc.Sequence not in list.__bases__ # list 并不是 Sequence 的子类\n", 51 | "assert isinstance([], abc.Sequence) # 但是 list 实例支持序列协议" 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": 6, 57 | "metadata": {}, 58 | "outputs": [ 59 | { 60 | "name": "stdout", 61 | "output_type": "stream", 62 | "text": [ 63 | "0\n" 64 | ] 65 | }, 66 | { 67 | "ename": "TypeError", 68 | "evalue": "Can't instantiate abstract class FailedSized with abstract methods __len__", 69 | "output_type": "error", 70 | "traceback": [ 71 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 72 | "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", 73 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 13\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mNormalSized\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 14\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 15\u001b[0;31m \u001b[0mf\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mFailedSized\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# 协议未实现,Python 会阻止对象实例化\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", 74 | "\u001b[0;31mTypeError\u001b[0m: Can't instantiate abstract class FailedSized with abstract methods __len__" 75 | ] 76 | } 77 | ], 78 | "source": [ 79 | "# 在抽象基类上进行自己的实现\n", 80 | "from collections import abc\n", 81 | "\n", 82 | "class FailedSized(abc.Sized):\n", 83 | " pass\n", 84 | "\n", 85 | "\n", 86 | "class NormalSized(abc.Sized):\n", 87 | " def __len__(self):\n", 88 | " return 0\n", 89 | "\n", 90 | "\n", 91 | "n = NormalSized()\n", 92 | "print(len(n))\n", 93 | "f = FailedSized() # 基类的抽象协议未实现,Python 会阻止对象实例化" 94 | ] 95 | }, 96 | { 97 | "cell_type": "markdown", 98 | "metadata": {}, 99 | "source": [ 100 | "有一点需要注意:抽象基类上的方法并不都是抽象方法。 \n", 101 | "换句话说,想继承自抽象基类,只需要实现它上面**所有的抽象方法**即可,有些方法的实现是可选的。 \n", 102 | "比如 [`Sequence.__contains__`](https://github.com/python/cpython/blob/3.7/Lib/_collections_abc.py#L889),Python 对此有自己的实现(使用 `__iter__` 遍历自身,查找是否有相等的元素)。但如果你在 `Sequence` 之上实现的序列是有序的,则可以使用二分查找来覆盖 `__contains__` 方法,从而提高查找效率。\n", 103 | "\n", 104 | "我们可以使用 `__abstractmethods__` 属性来查看某个抽象基类上的抽象方法。这个抽象基类的子类必须实现这些方法,才可以被正常实例化。" 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": 22, 110 | "metadata": {}, 111 | "outputs": [ 112 | { 113 | "name": "stdout", 114 | "output_type": "stream", 115 | "text": [ 116 | "Legal class OK\n" 117 | ] 118 | }, 119 | { 120 | "ename": "TypeError", 121 | "evalue": "Can't instantiate abstract class IllegalClass with abstract methods some_method", 122 | "output_type": "error", 123 | "traceback": [ 124 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 125 | "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", 126 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[0ml\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mLegalClass\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 20\u001b[0m \u001b[0ml\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msome_method\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 21\u001b[0;31m \u001b[0mil\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mIllegalClass\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# Raises\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", 127 | "\u001b[0;31mTypeError\u001b[0m: Can't instantiate abstract class IllegalClass with abstract methods some_method" 128 | ] 129 | } 130 | ], 131 | "source": [ 132 | "# 自己定义一个抽象基类\n", 133 | "import abc\n", 134 | "\n", 135 | "# 使用元类的定义方式是 class SomeABC(metaclass=abc.ABCMeta)\n", 136 | "class SomeABC(abc.ABC):\n", 137 | " @abc.abstractmethod\n", 138 | " def some_method(self):\n", 139 | " raise NotImplementedError\n", 140 | "\n", 141 | " \n", 142 | "class IllegalClass(SomeABC):\n", 143 | " pass\n", 144 | "\n", 145 | "class LegalClass(SomeABC):\n", 146 | " def some_method(self):\n", 147 | " print('Legal class OK')\n", 148 | "\n", 149 | " \n", 150 | "l = LegalClass()\n", 151 | "l.some_method()\n", 152 | "il = IllegalClass() # Raises" 153 | ] 154 | }, 155 | { 156 | "cell_type": "markdown", 157 | "metadata": {}, 158 | "source": [ 159 | "## 虚拟子类\n", 160 | "使用 [`register`](https://docs.python.org/3/library/abc.html#abc.ABCMeta.register) 接口可以将某个类注册为某个 ABC 的“虚拟子类”。支持 `register` 直接调用注册,以及使用 `@register` 装饰器方式注册(其实这俩是一回事)。 \n", 161 | "注册后,使用 `isinstance` 以及实例化时,解释器将不会对虚拟子类做任何方法检查。 \n", 162 | "注意:虚拟子类不是子类,所以虚拟子类不会继承抽象基类的任何方法。" 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": 26, 168 | "metadata": {}, 169 | "outputs": [ 170 | { 171 | "name": "stdout", 172 | "output_type": "stream", 173 | "text": [ 174 | "Subclass: \n" 175 | ] 176 | }, 177 | { 178 | "name": "stderr", 179 | "output_type": "stream", 180 | "text": [ 181 | "Traceback (most recent call last):\n", 182 | " File \"\", line 31, in \n", 183 | " il.some_method() # 虚拟子类不是子类,不会从抽象基类上继承任何方法\n", 184 | "AttributeError: 'IllegalClass' object has no attribute 'some_method'\n", 185 | "Traceback (most recent call last):\n", 186 | " File \"\", line 36, in \n", 187 | " il.another_method()\n", 188 | "AttributeError: 'IllegalClass' object has no attribute 'another_method'\n" 189 | ] 190 | } 191 | ], 192 | "source": [ 193 | "# 虚拟子类\n", 194 | "import abc\n", 195 | "import traceback\n", 196 | "\n", 197 | "class SomeABC(abc.ABC):\n", 198 | " @abc.abstractmethod\n", 199 | " def some_method(self):\n", 200 | " raise NotImplementedError\n", 201 | " \n", 202 | " def another_method(self):\n", 203 | " print('Another')\n", 204 | " \n", 205 | " @classmethod\n", 206 | " def __subclasshook__(cls, subcls):\n", 207 | " \"\"\"\n", 208 | " 在 register 或者进行 isinstance 判断时进行子类检测\n", 209 | " https://docs.python.org/3/library/abc.html#abc.ABCMeta.__subclasshook__\n", 210 | " \"\"\"\n", 211 | " print('Subclass:', subcls)\n", 212 | " return True\n", 213 | "\n", 214 | "\n", 215 | "class IllegalClass:\n", 216 | " pass\n", 217 | "\n", 218 | "SomeABC.register(IllegalClass) # 注册\n", 219 | "il = IllegalClass()\n", 220 | "assert isinstance(il, IllegalClass)\n", 221 | "assert SomeABC not in IllegalClass.__mro__ # isinstance 会返回 True,但 IllegalClass 并不是 SomeABC 的子类\n", 222 | "try:\n", 223 | " il.some_method() # 虚拟子类不是子类,不会从抽象基类上继承任何方法\n", 224 | "except Exception as e:\n", 225 | " traceback.print_exc()\n", 226 | "\n", 227 | "try:\n", 228 | " il.another_method()\n", 229 | "except Exception as e:\n", 230 | " traceback.print_exc()\n" 231 | ] 232 | } 233 | ], 234 | "metadata": { 235 | "kernelspec": { 236 | "display_name": "Python 3", 237 | "language": "python", 238 | "name": "python3" 239 | }, 240 | "language_info": { 241 | "codemirror_mode": { 242 | "name": "ipython", 243 | "version": 3 244 | }, 245 | "file_extension": ".py", 246 | "mimetype": "text/x-python", 247 | "name": "python", 248 | "nbconvert_exporter": "python", 249 | "pygments_lexer": "ipython3", 250 | "version": "3.7.0" 251 | } 252 | }, 253 | "nbformat": 4, 254 | "nbformat_minor": 2 255 | } 256 | -------------------------------------------------------------------------------- /第12章:继承的优缺点.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 继承的优缺点\n", 8 | "> (我们)推出继承的初衷是让新手顺利使用只有专家才能设计出来的框架。 \n", 9 | "> ——Alan Klay, \"The Early History of Smalltalk\"\n", 10 | "\n", 11 | "本章探讨继承和子类化,重点是说明对 Python 而言尤为重要的两个细节:\n", 12 | "* 子类化内置类型的缺点\n", 13 | "* 多重继承和方法解析顺序(MRO)" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": {}, 19 | "source": [ 20 | "## 子类化类型的缺点\n", 21 | "基本上,内置类型的方法不会调用子类覆盖的方法,所以尽可能不要去子类化内置类型。 \n", 22 | "如果有需要使用 `list`, `dict` 等类,`collections` 模块中提供了用于用户继承的 `UserDict`、`userList` 和 `UserString`,这些类经过特殊设计,因此易于扩展。" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 2, 28 | "metadata": {}, 29 | "outputs": [ 30 | { 31 | "name": "stdout", 32 | "output_type": "stream", 33 | "text": [ 34 | "{'one': 1, 'two': [2, 2]}\n", 35 | "{'one': 1, 'two': [2, 2], 'three': 3}\n", 36 | "{'one': [1, 1], 'two': [2, 2]}\n", 37 | "{'one': [1, 1], 'two': [2, 2], 'three': [3, 3]}\n" 38 | ] 39 | } 40 | ], 41 | "source": [ 42 | "# 子类化内置类型的缺点\n", 43 | "class DoppelDict(dict):\n", 44 | " def __setitem__(self, key, value):\n", 45 | " super().__setitem__(key, [value] * 2)\n", 46 | "\n", 47 | "# 构造方法和 update 都不会调用子类的 __setitem__\n", 48 | "dd = DoppelDict(one=1)\n", 49 | "dd['two'] = 2\n", 50 | "print(dd)\n", 51 | "dd.update(three=3)\n", 52 | "print(dd)\n", 53 | "\n", 54 | "\n", 55 | "from collections import UserDict\n", 56 | "class DoppelDict2(UserDict):\n", 57 | " def __setitem__(self, key, value):\n", 58 | " super().__setitem__(key, [value] * 2)\n", 59 | "\n", 60 | "# UserDict 中,__setitem__ 对 update 起了作用,但构造函数依然不会调用 __setitem__\n", 61 | "dd = DoppelDict2(one=1)\n", 62 | "dd['two'] = 2\n", 63 | "print(dd)\n", 64 | "dd.update(three=3)\n", 65 | "print(dd)" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "metadata": {}, 71 | "source": [ 72 | "## 方法解析顺序(Method Resolution Order)\n", 73 | "权威论述:[The Python 2.3 Method Resolution Order](https://www.python.org/download/releases/2.3/mro/) \n", 74 | "> Moreover, unless you make strong use of multiple inheritance and you have non-trivial hierarchies, you don't need to understand the C3 algorithm, and you can easily skip this paper. \n", 75 | "\n", 76 | "Emmm…\n", 77 | "\n", 78 | "OK,提两句:\n", 79 | "1. 如果想查看某个类的方法解析顺序,可以访问该类的 `__mro__` 属性;\n", 80 | "2. 如果想绕过 MRO 访问某个父类的方法,可以直接调用父类上的非绑定方法。" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": 3, 86 | "metadata": {}, 87 | "outputs": [ 88 | { 89 | "name": "stdout", 90 | "output_type": "stream", 91 | "text": [ 92 | "(, , , , )\n", 93 | "D\n", 94 | "B\n", 95 | "C\n", 96 | "A\n" 97 | ] 98 | } 99 | ], 100 | "source": [ 101 | "class A:\n", 102 | " def f(self):\n", 103 | " print('A')\n", 104 | "\n", 105 | "\n", 106 | "class B(A):\n", 107 | " def f(self):\n", 108 | " print('B')\n", 109 | "\n", 110 | "\n", 111 | "class C(A):\n", 112 | " def f(self):\n", 113 | " print('C')\n", 114 | "\n", 115 | "\n", 116 | "class D(B, C):\n", 117 | " def f(self):\n", 118 | " print('D')\n", 119 | "\n", 120 | " def b_f(self):\n", 121 | " \"D -> B\"\n", 122 | " super().f()\n", 123 | "\n", 124 | " def c_f(self):\n", 125 | " \"B -> C\"\n", 126 | " super(B, self).f()\n", 127 | " # C.f(self)\n", 128 | " \n", 129 | " def a_f(self):\n", 130 | " \"C -> A\"\n", 131 | " super(C, self).f()\n", 132 | "\n", 133 | "\n", 134 | "print(D.__mro__)\n", 135 | "d = D()\n", 136 | "d.f()\n", 137 | "d.b_f()\n", 138 | "d.c_f()\n", 139 | "d.a_f()" 140 | ] 141 | }, 142 | { 143 | "cell_type": "markdown", 144 | "metadata": {}, 145 | "source": [ 146 | "## 处理多重继承\n", 147 | "书中列出来了一些处理多重继承的建议,以免做出令人费解和脆弱的继承设计:\n", 148 | "1. 把接口继承和实现继承区分开 \n", 149 | " 如果继承重用的是代码实现细节,通常可以换用组合和委托模式。\n", 150 | "2. 使用抽象基类显式表示接口 \n", 151 | " 如果基类的作用是定义接口,那就应该定义抽象基类。\n", 152 | "3. 通过混入(Mixin)重用代码 \n", 153 | " 如果一个类的作用是为多个不相关的子类提供方法实现,从而实现重用,但不体现实际的“上下级”关系,那就应该明确地将这个类定义为**混入类**(Mixin class)。关于 Mixin(我还是习惯英文名),可以看 Python3-Cookbook 的[《利用Mixins扩展类功能》](https://python3-cookbook.readthedocs.io/zh_CN/latest/c08/p18_extending_classes_with_mixins.html)章节。\n", 154 | "4. 在名称中明确指出混入 \n", 155 | " 在类名里使用 `XXMixin` 写明这个类是一个 Mixin.\n", 156 | "5. 抽象基类可以作为混入,反过来则不成立\n", 157 | "6. 不要子类化多个具体类 \n", 158 | " 在设计子类时,不要在多个**具体基类**上实现多继承。一个子类最好只继承自一个具体基类,其余基类最好应为 Mixin,用于提供增强功能。\n", 159 | "7. 为用户提供聚合类 \n", 160 | " 如果抽象基类或 Mixin 的组合对客户代码非常有用,那就替客户实现一个包含多继承的聚合类,这样用户可以直接继承自你的聚合类,而不需要再引入 Mixin.\n", 161 | "8. “优先使用对象组合,而不是类继承” \n", 162 | " 组合和委托可以代替混入,把行为提供给不同的类,不过这些方法不能取代接口继承去**定义类型层次结构**。" 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "metadata": {}, 168 | "source": [ 169 | "两个实际例子: \n", 170 | "* 很老的 `tkinter` 称为了反例,那个时候的人们还没有充分认识到多重继承的缺点;\n", 171 | "* 现代的 `Django` 很好地利用了继承和 Mixin。它提供了非常多的 `View` 类,鼓励用户去使用这些类以免除大量模板代码。" 172 | ] 173 | } 174 | ], 175 | "metadata": { 176 | "kernelspec": { 177 | "display_name": "Python 3", 178 | "language": "python", 179 | "name": "python3" 180 | }, 181 | "language_info": { 182 | "codemirror_mode": { 183 | "name": "ipython", 184 | "version": 3 185 | }, 186 | "file_extension": ".py", 187 | "mimetype": "text/x-python", 188 | "name": "python", 189 | "nbconvert_exporter": "python", 190 | "pygments_lexer": "ipython3", 191 | "version": "3.7.0" 192 | } 193 | }, 194 | "nbformat": 4, 195 | "nbformat_minor": 2 196 | } 197 | -------------------------------------------------------------------------------- /第13章:正确重载运算符.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 正确重载运算符\n", 8 | "> 有些事情让我不安,比如运算符重载。我决定不支持运算符重载,这完全是个个人选择,因为我见过太多 C++ 程序员滥用它。\n", 9 | "> ——James Gosling, Java 之父\n", 10 | "\n", 11 | "本章讨论的内容是:\n", 12 | "* Python 如何处理中缀运算符(如 `+` 和 `|`)中不同类型的操作数\n", 13 | "* 使用鸭子类型或显式类型检查处理不同类型的操作数\n", 14 | "* 众多比较运算符(如 `==`、`>`、`<=` 等等)的特殊行为\n", 15 | "* 增量赋值运算符(如 `+=`)的默认处理方式和重载方式" 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "metadata": {}, 21 | "source": [ 22 | "重载运算符,如果使用得当,可以让代码更易于阅读和编写。 \n", 23 | "Python 出于灵活性、可用性和安全性方面的平衡考虑,对运算符重载做了一些限制:\n", 24 | "* 不能重载内置类型的运算符\n", 25 | "* 不能新建运算符\n", 26 | "* 计算符 `is`、`and`、`or`、`not` 不能重载\n", 27 | "\n", 28 | "Python 算数运算符对应的魔术方法可以见[这里](https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types)。\n", 29 | "\n", 30 | "一个小知识:二元运算符 `+` 和 `-` 对应的魔术方法是 `__add__` 和 `__sub__`,而一元运算符 `+` 和 `-` 对应的魔术方法是 `__pos__` 和 `__neg__`." 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "## 反向运算符\n", 38 | "> 为了支持涉及不同类型的运算,Python 为中缀运算符特殊方法提供了特殊的分派机制。对表达式 `a + b` 来说,解释器会执行以下几步操作。\n", 39 | "> 1. 如果 a 有 `__add__` 方法,而且返回值不是 `NotImplemented`,调用 `a.__add__`,然后返回结果。\n", 40 | "> 2. 如果 a 没有 `__add__` 方法,或者调用 `__add__` 方法返回 `NotImplemented`,检查 b 有没有 `__radd__` 方法,如果有,而且没有返回 `NotImplemented`,调用 `b.__radd__`,然后返回结果。\n", 41 | "> 3. 如果 b 没有 `__radd__` 方法,或者调用 `__radd__` 方法返回 `NotImplemented`,抛出 `TypeError`, 并在错误消息中指明操作数类型不支持。\n", 42 | "\n", 43 | "这样一来,只要运算符两边的任何一个对象正确实现了运算方法,就可以正常实现二元运算操作。\n", 44 | "\n", 45 | "小知识: \n", 46 | "* `NotImplemented` 是一个特殊的单例值,要 `return`;而 `NotImplementedError` 是一个异常,要 `raise`.\n", 47 | "* Python 3.5 新引入了 `@` 运算符,用于点积乘法,对应的魔术方法是 [`__matmul__`](https://docs.python.org/3/reference/datamodel.html#object.__matmul__).\n", 48 | "* 进行 `!=` 运算时,如果双方对象都没有实现 `__ne__`,解释器会尝试 `__eq__` 操作,并将得到的结果**取反**。\n", 49 | "\n", 50 | "放在这里有点吓人的小知识:\n", 51 | "* Python 在进行 `==` 运算时,如果运算符两边的 `__eq__` 都失效了,解释器会用两个对象的 id 做比较\\_(:з」∠)\\_。_书中用了“最后一搏”这个词…真的有点吓人。_" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "## 运算符分派\n", 59 | "有的时候,运算符另一边的对象可能会出现多种类型:比如对向量做乘法时,另外一个操作数可能是向量,也可能是一个标量。此时,需要在方法实现中,根据操作数的类型进行分派。 \n", 60 | "此时有两种选择:\n", 61 | "1. 尝试直接运算,如果有问题,捕获 `TypeError` 异常;\n", 62 | "2. 在运算前使用 `isinstance` 进行类型判断,在收到可接受类型时在进行运算。 \n", 63 | "判断类型时,应进行鸭子类型的判断。应该使用 `isinstance(other, numbers.Integral)`,而不是用 `isinstance(other, int)`,这是之前的知识点。 \n", 64 | "\n", 65 | "不过,在类上定义方法时,是不能用 `functools.singledispatch` 进行单分派的,因为第一个参数是 `self`,而不是 `o`." 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": 3, 71 | "metadata": {}, 72 | "outputs": [ 73 | { 74 | "name": "stdout", 75 | "output_type": "stream", 76 | "text": [ 77 | "12 \n", 78 | "None \n", 79 | "12 \n" 80 | ] 81 | } 82 | ], 83 | "source": [ 84 | "# 一个就地运算符的错误示范\n", 85 | "class T:\n", 86 | " def __init__(self, s):\n", 87 | " self.s = s\n", 88 | "\n", 89 | " def __str__(self):\n", 90 | " return self.s\n", 91 | "\n", 92 | " def __add__(self, o):\n", 93 | " return self.s + o\n", 94 | "\n", 95 | " def __iadd__(self, o):\n", 96 | " self.s += o\n", 97 | " # 这里必须要返回一个引用,用于传给 += 左边的引用变量\n", 98 | " # return self\n", 99 | "\n", 100 | "\n", 101 | "t = T('1')\n", 102 | "t1 = t\n", 103 | "w = t + '2'\n", 104 | "print(w, type(w))\n", 105 | "t += '2' # t = t.__iadd__('2')\n", 106 | "print(t, type(t)) # t 被我们搞成了 None\n", 107 | "print(t1, type(t1))" 108 | ] 109 | } 110 | ], 111 | "metadata": { 112 | "kernelspec": { 113 | "display_name": "Python 3", 114 | "language": "python", 115 | "name": "python3" 116 | }, 117 | "language_info": { 118 | "codemirror_mode": { 119 | "name": "ipython", 120 | "version": 3 121 | }, 122 | "file_extension": ".py", 123 | "mimetype": "text/x-python", 124 | "name": "python", 125 | "nbconvert_exporter": "python", 126 | "pygments_lexer": "ipython3", 127 | "version": "3.7.0" 128 | } 129 | }, 130 | "nbformat": 4, 131 | "nbformat_minor": 2 132 | } 133 | -------------------------------------------------------------------------------- /第14章:可迭代的对象、迭代器和生成器.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 可迭代的对象、迭代器和生成器\n", 8 | "\n", 9 | "> 当我在自己的程序中发现用到了模式,我觉得这就表明某个地方出错了。程序的形式应该仅仅反映它所要解决的问题。代码中其他任何外加的形式都是一个信号,(至少对我来说)表明我对问题的抽象还不够深——这通常意味着自己正在手动完成事情,本应该通过写代码来让宏的扩展自动实现。\n", 10 | "> ——Paul Graham, Lisp 黑客和风险投资人\n", 11 | "\n", 12 | "Python 内置了迭代器模式,用于进行**惰性运算**,按需求一次获取一个数据项,避免不必要的提前计算。\n", 13 | "\n", 14 | "迭代器在 Python 中并不是一个具体类型的对象,更多地使指一个具体**协议**。" 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "## 迭代器协议\n", 22 | "Python 解释器在迭代一个对象时,会自动调用 `iter(x)`。 \n", 23 | "内置的 `iter` 函数会做以下操作:\n", 24 | "1. 检查对象是否实现了 `__iter__` 方法(`abc.Iterable`),若实现,且返回的结果是个迭代器(`abc.Iterator`),则调用它,获取迭代器并返回;\n", 25 | "2. 若没实现,但实现了 `__getitem__` 方法(`abc.Sequence`),若实现则尝试从 0 开始按顺序获取元素并返回;\n", 26 | "3. 以上尝试失败,抛出 `TypeError`,表明对象不可迭代。\n", 27 | "\n", 28 | "判断一个对象是否可迭代,最好的方法不是用 `isinstance` 来判断,而应该直接尝试调用 `iter` 函数。\n", 29 | "\n", 30 | "注:可迭代对象和迭代器不一样。从鸭子类型的角度看,可迭代对象 `Iterable` 要实现 `__iter__`,而迭代器 `Iterator` 要实现 `__next__`. 不过,迭代器上也实现了 `__iter__`,用于[返回自身](https://github.com/python/cpython/blob/3.7/Lib/_collections_abc.py#L268)。" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "## 迭代器的具体实现\n", 38 | "《设计模式:可复用面向对象软件的基础》一书讲解迭代器设计模式时,在“适用性”一 节中说:\n", 39 | "迭代器模式可用来:\n", 40 | "* 访问一个聚合对象的内容而无需暴露它的内部表示\n", 41 | "* 支持对聚合对象的多种遍历\n", 42 | "* 为遍历不同的聚合结构提供一个统一的接口(即支持多态迭代)\n", 43 | "\n", 44 | "为了“支持多种遍历”,必须能从同一个可迭代的实例中获取多个**独立**的迭代器,而且各个迭代器要能维护自身的内部状态,因此这一模式正确的实现方式是,每次调用 `iter(my_iterable)` 都新建一个独立的迭代器。这就是为什么这个示例需要定义 `SentenceIterator` 类。\n", 45 | "\n", 46 | "所以,不应该把 Sentence 本身作为一个迭代器,否则每次调用 `iter(sentence)` 时返回的都是自身,就无法进行多次迭代了。" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 4, 52 | "metadata": {}, 53 | "outputs": [ 54 | { 55 | "name": "stdout", 56 | "output_type": "stream", 57 | "text": [ 58 | "Return·a·list·of·all·non·overlapping·matches·in·the·string·" 59 | ] 60 | } 61 | ], 62 | "source": [ 63 | "# 通过实现迭代器协议,让一个对象变得可迭代\n", 64 | "import re\n", 65 | "from collections import abc\n", 66 | "\n", 67 | "\n", 68 | "class Sentence:\n", 69 | " def __init__(self, sentence):\n", 70 | " self.sentence = sentence\n", 71 | " self.words = re.findall(r'\\w+', sentence)\n", 72 | "\n", 73 | " def __iter__(self):\n", 74 | " \"\"\"返回 iter(self) 的结果\"\"\"\n", 75 | " return SentenceIterator(self.words)\n", 76 | " \n", 77 | "\n", 78 | "# 推荐的做法是对迭代器对象进行单独实现\n", 79 | "class SentenceIterator(abc.Iterator):\n", 80 | " def __init__(self, words):\n", 81 | " self.words = words\n", 82 | " self._index = 0\n", 83 | "\n", 84 | " def __next__(self):\n", 85 | " \"\"\"调用时返回下一个对象\"\"\"\n", 86 | " try:\n", 87 | " word = self.words[self._index]\n", 88 | " except IndexError:\n", 89 | " raise StopIteration()\n", 90 | " else:\n", 91 | " self._index += 1\n", 92 | " \n", 93 | " return word\n", 94 | "\n", 95 | "\n", 96 | " \n", 97 | "sentence = Sentence('Return a list of all non-overlapping matches in the string.')\n", 98 | "assert isinstance(sentence, abc.Iterable) # 实现了 __iter__,就支持 Iterable 协议\n", 99 | "assert isinstance(iter(sentence), abc.Iterator)\n", 100 | "for word in sentence:\n", 101 | " print(word, end='·')" 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "metadata": {}, 107 | "source": [ 108 | "上面的例子中,我们的 `SentenceIterator` 对象继承自 `abc.Iterator` 通过了迭代器测试。而且 `Iterator` 替我们实现了 `__iter__` 方法。 \n", 109 | "但是,如果我们不继承它,我们就需要同时实现 `__next__` 抽象方法和*实际迭代中并不会用到的* `__iter__` 非抽象方法,才能通过 `Iterator` 测试。" 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "metadata": {}, 115 | "source": [ 116 | "## 生成器函数\n", 117 | "如果懒得自己写一个迭代器,可以直接用 Python 的生成器函数来在调用 `__iter__` 时生成一个迭代器。\n", 118 | "\n", 119 | "注:在 Python 社区中,大家并没有对“生成器”和“迭代器”两个概念做太多区分,很多人是混着用的。不过无所谓啦。" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": null, 125 | "metadata": {}, 126 | "outputs": [], 127 | "source": [ 128 | "# 使用生成器函数来帮我们创建迭代器\n", 129 | "import re\n", 130 | "\n", 131 | "\n", 132 | "class Sentence:\n", 133 | " def __init__(self, sentence):\n", 134 | " self.sentence = sentence\n", 135 | " self.words = re.findall(r'\\w+', sentence)\n", 136 | "\n", 137 | " def __iter__(self):\n", 138 | " for word in self.words:\n", 139 | " yield word\n", 140 | " return\n", 141 | "\n", 142 | "sentence = Sentence('Return a list of all non-overlapping matches in the string.')\n", 143 | "for word in sentence:\n", 144 | " print(word, end='·')" 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": null, 150 | "metadata": {}, 151 | "outputs": [], 152 | "source": [ 153 | "# 使用 re.finditer 来惰性生成值\n", 154 | "# 使用生成器表达式(很久没用过了)\n", 155 | "import re\n", 156 | "\n", 157 | "\n", 158 | "class Sentence:\n", 159 | " def __init__(self, sentence):\n", 160 | " self.re_word = re.compile(r'\\w+')\n", 161 | " self.sentence = sentence\n", 162 | "\n", 163 | " def __iter__(self):\n", 164 | " return (match.group()\n", 165 | " for match in self.re_word.finditer(self.sentence))\n", 166 | "\n", 167 | "sentence = Sentence('Return a list of all non-overlapping matches in the string.')\n", 168 | "for word in sentence:\n", 169 | " print(word, end='·')" 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": null, 175 | "metadata": {}, 176 | "outputs": [], 177 | "source": [ 178 | "# 实用模块\n", 179 | "import itertools\n", 180 | "\n", 181 | "# takewhile & dropwhile\n", 182 | "print(list(itertools.takewhile(lambda x: x < 3, [1, 5, 2, 4, 3])))\n", 183 | "print(list(itertools.dropwhile(lambda x: x < 3, [1, 5, 2, 4, 3])))\n", 184 | "# zip\n", 185 | "print(list(zip(range(5), range(3))))\n", 186 | "print(list(itertools.zip_longest(range(5), range(3))))\n", 187 | "\n", 188 | "# itertools.groupby\n", 189 | "animals = ['rat', 'bear', 'duck', 'bat', 'eagle', 'shark', 'dolphin', 'lion']\n", 190 | "# groupby 需要假定输入的可迭代对象已经按照分组标准进行排序(至少同组的元素要连在一起)\n", 191 | "print('----')\n", 192 | "for length, animal in itertools.groupby(animals, len):\n", 193 | " print(length, list(animal))\n", 194 | "print('----')\n", 195 | "animals.sort(key=len)\n", 196 | "for length, animal in itertools.groupby(animals, len):\n", 197 | " print(length, list(animal))\n", 198 | "print('---')\n", 199 | "# tee\n", 200 | "g1, g2 = itertools.tee('abc', 2)\n", 201 | "print(list(zip(g1, g2)))" 202 | ] 203 | }, 204 | { 205 | "cell_type": "code", 206 | "execution_count": 7, 207 | "metadata": {}, 208 | "outputs": [ 209 | { 210 | "name": "stdout", 211 | "output_type": "stream", 212 | "text": [ 213 | "[1, 2, 3, 4, 5] [1, 2, 3, 4, 5]\n" 214 | ] 215 | } 216 | ], 217 | "source": [ 218 | "# 使用 yield from 语句可以在生成器函数中直接迭代一个迭代器\n", 219 | "from itertools import chain\n", 220 | "\n", 221 | "def my_itertools_chain(*iterators):\n", 222 | " for iterator in iterators:\n", 223 | " yield from iterator\n", 224 | "\n", 225 | "chain1 = my_itertools_chain([1, 2], [3, 4, 5])\n", 226 | "chain2 = chain([1, 2, 3], [4, 5])\n", 227 | "print(list(chain1), list(chain2))" 228 | ] 229 | }, 230 | { 231 | "cell_type": "markdown", 232 | "metadata": {}, 233 | "source": [ 234 | "`iter` 函数还有一个鲜为人知的用法:传入两个参数,使用常规的函数或任何可调用的对象创建迭代器。这样使用时,第一个参数必须是可调用的对象,用于不断调用(没有参数),产出各个值;第二个值是哨符,这是个标记值,当可调用的对象返回这个值时,触发迭代器抛出 StopIteration 异常,而不产出哨符。" 235 | ] 236 | }, 237 | { 238 | "cell_type": "code", 239 | "execution_count": null, 240 | "metadata": {}, 241 | "outputs": [], 242 | "source": [ 243 | "# iter 的神奇用法\n", 244 | "# iter(callable, sentinel)\n", 245 | "import random\n", 246 | "\n", 247 | "def rand():\n", 248 | " return random.randint(1, 6)\n", 249 | "# 不停调用 rand(), 直到产出一个 5\n", 250 | "print(list(iter(rand, 5)))" 251 | ] 252 | } 253 | ], 254 | "metadata": { 255 | "kernelspec": { 256 | "display_name": "Python 3", 257 | "language": "python", 258 | "name": "python3" 259 | }, 260 | "language_info": { 261 | "codemirror_mode": { 262 | "name": "ipython", 263 | "version": 3 264 | }, 265 | "file_extension": ".py", 266 | "mimetype": "text/x-python", 267 | "name": "python", 268 | "nbconvert_exporter": "python", 269 | "pygments_lexer": "ipython3", 270 | "version": "3.7.0" 271 | } 272 | }, 273 | "nbformat": 4, 274 | "nbformat_minor": 2 275 | } 276 | -------------------------------------------------------------------------------- /第15章:上下文管理器和else块.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 上下文管理和 else 块\n", 8 | "> 最终,上下文管理器可能几乎与子程序(subroutine)本身一样重要。目前,我们只了解了上下文管理器的皮毛……Basic 语言有 with 语句,而且很多语言都有。但是,在各种语言中 with 语句的作用不同,而且做的都是简单的事,虽然可以避免不断使 用点号查找属性,但是不会做事前准备和事后清理。不要觉得名字一样,就意味着作用也一样。with 语句是非常了不起的特性。 \n", 9 | "> ——Raymond Hettinger, 雄辩的 Python 布道者\n", 10 | "\n", 11 | "本章讨论的特性有:\n", 12 | "* with 语句和上下文管理器\n", 13 | "* for、while 和 try 语句的 else 语句" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": {}, 19 | "source": [ 20 | "## if 语句之外的 else 块\n", 21 | "> else 子句的行为如下。\n", 22 | ">\n", 23 | "> for: 仅当 for 循环运行完毕时(即 for 循环没有被 break 语句中止)才运行 else 块。 \n", 24 | "> while: 仅当 while 循环因为条件为**假值**而退出时(即 while 循环没有被 break 语句中止)才运行 else 块。 \n", 25 | "> try: 仅当try 块中没有异常抛出时才运行else 块。官方文档(https://docs.python.org/3/ reference/compound_stmts.html)还指出:“else 子句抛出的异常不会由前面的 except 子句处理。”\n", 26 | "\n", 27 | "\n", 28 | "## try 和 else\n", 29 | "为了清晰和准确,`try` 块中应该之抛出预期异常的语句。因此,下面这样写更好:\n", 30 | "```python\n", 31 | "try:\n", 32 | " dangerous_call()\n", 33 | " # 不要把 after_call() 放在这里\n", 34 | " # 虽然放在这里时的代码运行逻辑是一样的\n", 35 | "except OSError:\n", 36 | " log('OSError')\n", 37 | "else:\n", 38 | " after_call()\n", 39 | "```" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": {}, 45 | "source": [ 46 | "## 上下文管理器\n", 47 | "上下文管理器可以在 `with` 块中改变程序的上下文,并在块结束后将其还原。" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": 2, 53 | "metadata": {}, 54 | "outputs": [ 55 | { 56 | "name": "stdout", 57 | "output_type": "stream", 58 | "text": [ 59 | "ecilA\n", 60 | "YKCOWREBBAJ\n", 61 | "JABBERWOCKY\n", 62 | "Back to normal\n" 63 | ] 64 | } 65 | ], 66 | "source": [ 67 | "# 上下文管理器与 __enter__ 方法返回对象的区别\n", 68 | "\n", 69 | "class LookingGlass:\n", 70 | " def __enter__(self):\n", 71 | " import sys\n", 72 | " self.original_write = sys.stdout.write\n", 73 | " sys.stdout.write = self.reverse_write\n", 74 | " return 'JABBERWOCKY'\n", 75 | "\n", 76 | " def reverse_write(self, text):\n", 77 | " self.original_write(text[::-1])\n", 78 | " \n", 79 | " def __exit__(self, exc_type, exc_value, traceback):\n", 80 | " import sys\n", 81 | " sys.stdout.write = self.original_write\n", 82 | " if exc_type is ZeroDivisionError:\n", 83 | " print('Please DO NOT divide by zero!')\n", 84 | " return True\n", 85 | "\n", 86 | "with LookingGlass() as what: # 这里的 what 是 __enter__ 的返回值\n", 87 | " print('Alice')\n", 88 | " print(what)\n", 89 | "\n", 90 | "print(what)\n", 91 | "print('Back to normal')" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": 3, 97 | "metadata": {}, 98 | "outputs": [ 99 | { 100 | "name": "stdout", 101 | "output_type": "stream", 102 | "text": [ 103 | "hhhhha\n", 104 | "gfedcba\n", 105 | "abcdefg\n" 106 | ] 107 | } 108 | ], 109 | "source": [ 110 | "# contextmanager 的使用\n", 111 | "\n", 112 | "import contextlib\n", 113 | "\n", 114 | "@contextlib.contextmanager\n", 115 | "def looking_glass():\n", 116 | " import sys\n", 117 | " original_write = sys.stdout.write\n", 118 | " sys.stdout.write = lambda text: original_write(text[::-1])\n", 119 | " msg = ''\n", 120 | " try:\n", 121 | " # 在当前上下文中抛出的异常,会通过 yield 语句抛出\n", 122 | " yield 'abcdefg'\n", 123 | " except ZeroDivisionError:\n", 124 | " msg = 'Please DO NOT divide by zero!'\n", 125 | " finally:\n", 126 | " # 为了避免上下文内抛出异常导致退出失败\n", 127 | " # 所以退出上下文时一定要使用 finally 语句\n", 128 | " sys.stdout.write = original_write\n", 129 | " if msg:\n", 130 | " print(msg)\n", 131 | "\n", 132 | "# 写完以后感觉这个用法跟 pytest.fixture 好像啊\n", 133 | "\n", 134 | "with looking_glass() as what:\n", 135 | " print('ahhhhh')\n", 136 | " print(what)\n", 137 | "\n", 138 | "print(what)" 139 | ] 140 | }, 141 | { 142 | "cell_type": "markdown", 143 | "metadata": {}, 144 | "source": [ 145 | "此外,[`contextlib.ExitStack`](https://docs.python.org/3/library/contextlib.html#contextlib.ExitStack) 在某些需要进入未知多个上下文管理器时可以方便管理所有的上下文。具体使用方法可以看文档中的示例。" 146 | ] 147 | } 148 | ], 149 | "metadata": { 150 | "kernelspec": { 151 | "display_name": "Python 3", 152 | "language": "python", 153 | "name": "python3" 154 | }, 155 | "language_info": { 156 | "codemirror_mode": { 157 | "name": "ipython", 158 | "version": 3 159 | }, 160 | "file_extension": ".py", 161 | "mimetype": "text/x-python", 162 | "name": "python", 163 | "nbconvert_exporter": "python", 164 | "pygments_lexer": "ipython3", 165 | "version": "3.7.0" 166 | } 167 | }, 168 | "nbformat": 4, 169 | "nbformat_minor": 2 170 | } 171 | -------------------------------------------------------------------------------- /第16章:协程.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 协程\n", 8 | "> 如果 Python 书籍有一定的指导作用,那么(协程就是)文档最匮乏、最鲜为人知的 Python 特性,因此表面上看是最无用的特性。\n", 9 | "> ——David Beazley, Python 图书作者\n", 10 | "\n", 11 | "在“生成器”章节中我们认识了 `yield` 语句。但 `yield` 的作用不只是在生成器运行过程中**返回**一个值,还可以从调用方拿回来一个值(`.send(datum)`),甚至一个异常(`.throw(exc)`)。 \n", 12 | "由此依赖,`yield` 语句就成为了一种流程控制工具,使用它可以实现协作式多任务:协程可以把控制器让步给中心调度程序,从而激活其它的协程。\n", 13 | "\n", 14 | "从根本上把 yield 视作控制流程的方式,这样就好理解协程了。\n", 15 | "\n", 16 | "本章涵盖以下话题:\n", 17 | "* 生成器作为协程使用时的行为和状态\n", 18 | "* 使用装饰器自动预激协程\n", 19 | "* 调用方如何使用生成器对象的 `.close()` 和 `.throw(...)` 方法控制协程\n", 20 | "* 协程终止时如何返回值\n", 21 | "* `yield from` 新句法的用途和语义\n", 22 | "* 使用案例——使用协程管理仿真系统中的并发活动" 23 | ] 24 | }, 25 | { 26 | "cell_type": "markdown", 27 | "metadata": {}, 28 | "source": [ 29 | "协程的四种状态:\n", 30 | "* `GEN_CREATED`: 等待开始执行\n", 31 | "* `GEN_RUNNING`: 解释器正在执行\n", 32 | "* `GEN_SUSPENDED`: 在 `yield` 表达式处暂停\n", 33 | "* `GEN_CLOSED`: 执行结束" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "# 最简单的协程使用演示\n", 43 | "from inspect import getgeneratorstate\n", 44 | "\n", 45 | "def simple_coroutine():\n", 46 | " # GEN_RUNNING 状态\n", 47 | " print(\"Coroutine started\")\n", 48 | " x = yield\n", 49 | " print(\"Couroutine received:\", x)\n", 50 | "\n", 51 | "my_coro = simple_coroutine()\n", 52 | "print(getgeneratorstate(my_coro)) # GEN_CREATED\n", 53 | "next(my_coro) # “预激”(prime)协程,使它能够接收来自外部的值\n", 54 | "print(getgeneratorstate(my_coro)) # GEN_SUSPENDED\n", 55 | "try:\n", 56 | " my_coro.send(42)\n", 57 | "except StopIteration as e:\n", 58 | " print('StopIteration')\n", 59 | "print(getgeneratorstate(my_coro)) # GEN_CLOSED" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": 13, 65 | "metadata": {}, 66 | "outputs": [ 67 | { 68 | "name": "stdout", 69 | "output_type": "stream", 70 | "text": [ 71 | "1 1\n", 72 | "2 3\n", 73 | "3 6\n", 74 | "4 10\n", 75 | "5 15\n", 76 | "6 21\n", 77 | "7 28\n", 78 | "8 36\n", 79 | "9 45\n", 80 | "10 55\n" 81 | ] 82 | }, 83 | { 84 | "ename": "StopIteration", 85 | "evalue": "", 86 | "output_type": "error", 87 | "traceback": [ 88 | "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", 89 | "\u001b[1;31mStopIteration\u001b[0m Traceback (most recent call last)", 90 | "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 11\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0masum\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 12\u001b[0m \u001b[0masum\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mclose\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;31m# 如果协程不会自己关闭,我们还可以手动终止协程\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 13\u001b[1;33m \u001b[0masum\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m11\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", 91 | "\u001b[1;31mStopIteration\u001b[0m: " 92 | ] 93 | } 94 | ], 95 | "source": [ 96 | "# 产出多个值的协程\n", 97 | "def async_sum(a=0):\n", 98 | " s = a\n", 99 | " while True:\n", 100 | " n = yield s\n", 101 | " s += n\n", 102 | "\n", 103 | "asum = async_sum()\n", 104 | "next(asum)\n", 105 | "for i in range(1, 11):\n", 106 | " print(i, asum.send(i))\n", 107 | "asum.close() # 如果协程不会自己关闭,我们还可以手动终止协程\n", 108 | "asum.send(11)" 109 | ] 110 | }, 111 | { 112 | "cell_type": "markdown", 113 | "metadata": {}, 114 | "source": [ 115 | "协程手动终止的注意事项: \n", 116 | "调用 `gen.closed()` 后,生成器内的 `yield` 语句会抛出 `GeneratorExit` 异常。如果生成器没有处理这个异常,或者抛出了 `StopIteration` 异常(通常是指运行到结尾),调用方不会报错。 \n", 117 | "如果收到 `GeneratorExit` 异常,生成器一定不能产出值,否则解释器会抛出 `RuntimeError` 异常。生成器抛出的其他异常会向上冒泡,传给调用方。\n", 118 | "\n", 119 | "协程内异常处理的示例见[官方示例 Repo](https://github.com/fluentpython/example-code/blob/master/16-coroutine/coro_finally_demo.py)。\n" 120 | ] 121 | }, 122 | { 123 | "cell_type": "markdown", 124 | "metadata": {}, 125 | "source": [ 126 | "## yield from\n", 127 | "在协程中,`yield from` 语句的主要功能是打开双向通道,把外层的调用方与内层的子生成器连接起来,这样二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程中添加大量处理异常的样板代码。 \n", 128 | "这种夹在中间的生成器,我们称它为“委派生成器”。\n", 129 | "\n", 130 | "子生成器迭代结束后返回(`return`)的值,会交给 `yield from` 函数。\n", 131 | "\n", 132 | "注意:`yield from` 语句会预激生成器,所以与用来预激生成器的装饰器不能放在一起用,否则会出问题。" 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": 16, 138 | "metadata": {}, 139 | "outputs": [ 140 | { 141 | "name": "stdout", 142 | "output_type": "stream", 143 | "text": [ 144 | "1\n", 145 | "3\n", 146 | "6\n", 147 | "10\n", 148 | "15\n", 149 | "21\n", 150 | "28\n", 151 | "36\n", 152 | "45\n", 153 | "55\n", 154 | "Caught exception \n", 155 | "Final result: 55\n" 156 | ] 157 | }, 158 | { 159 | "ename": "StopIteration", 160 | "evalue": "", 161 | "output_type": "error", 162 | "traceback": [ 163 | "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", 164 | "\u001b[1;31mStopIteration\u001b[0m Traceback (most recent call last)", 165 | "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 19\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0masum\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 20\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 21\u001b[1;33m \u001b[0m_\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0masum\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mthrow\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mValueError\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", 166 | "\u001b[1;31mStopIteration\u001b[0m: " 167 | ] 168 | } 169 | ], 170 | "source": [ 171 | "# 委派生成器\n", 172 | "def async_sum(a=0):\n", 173 | " s = a\n", 174 | " while True:\n", 175 | " try:\n", 176 | " n = yield s\n", 177 | " except Exception as e:\n", 178 | " print('Caught exception', e)\n", 179 | " return s\n", 180 | " s += n\n", 181 | "\n", 182 | "def middleware():\n", 183 | " x = yield from async_sum()\n", 184 | " print('Final result:', x)\n", 185 | " \n", 186 | "asum = middleware()\n", 187 | "next(asum)\n", 188 | "for i in range(1, 11):\n", 189 | " print(asum.send(i))\n", 190 | "\n", 191 | "_ = asum.throw(ValueError)" 192 | ] 193 | }, 194 | { 195 | "cell_type": "markdown", 196 | "metadata": {}, 197 | "source": [ 198 | "关于“任务式”协程,书中给出了一个[简单的例子](https://github.com/fluentpython/example-code/blob/master/16-coroutine/taxi_sim.py),用于执行[离散事件仿真](https://zhuanlan.zhihu.com/p/22689081),仔细研究一下可以对协程有个简单的认识。" 199 | ] 200 | } 201 | ], 202 | "metadata": { 203 | "kernelspec": { 204 | "display_name": "Python 3", 205 | "language": "python", 206 | "name": "python3" 207 | }, 208 | "language_info": { 209 | "codemirror_mode": { 210 | "name": "ipython", 211 | "version": 3 212 | }, 213 | "file_extension": ".py", 214 | "mimetype": "text/x-python", 215 | "name": "python", 216 | "nbconvert_exporter": "python", 217 | "pygments_lexer": "ipython3", 218 | "version": "3.7.0" 219 | } 220 | }, 221 | "nbformat": 4, 222 | "nbformat_minor": 2 223 | } 224 | -------------------------------------------------------------------------------- /第17章:使用期物处理并发.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 使用期物处理并发\n", 8 | "> 抨击线程的往往是系统程序员,他们考虑的使用场景对一般的应用程序员来说,也许一生都不会遇到……应用程序员遇到的使用场景,99% 的情况下只需知道如何派生一堆独立的线程,然后用队列收集结果。 \n", 9 | "> Michele Simionato, 深度思考 Python 的人\n", 10 | "\n", 11 | "本章主要讨论 `concurrent.futures` 模块,并介绍“期物”(future)的概念。\n", 12 | "\n", 13 | "我们在进行 IO 密集型并发编程(如批量下载)时,经常会考虑使用多线程场景来替代依序下载的方案,以提高下载效率。 \n", 14 | "在 IO 密集型任务中,如果代码写的正确,那么不管使用哪种并发策略(使用线程或 `asyncio` 包),吞吐量都要比依序执行的代码高很多。" 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "## 期物\n", 22 | "期物(Future)表示“**将要**执行并返回结果的任务”,这个概念与 JavaScript 的 `Promise` 对象较为相似。\n", 23 | "\n", 24 | "Python 3.4 起,标准库中有两个 Future 类:`concurrent.futures.Future` 和 `asyncio.Future`。这两个类的作用相同:`Future` 类的实例表示可能已经完成或尚未完成的延迟计算。 \n", 25 | "通常情况下自己不应该创建期物或改变期物的状态,而只能由并发框架实例化。 \n", 26 | "我们将某个任务交给并发框架后,这个任务将会由框架来进行调度,我们无法改变它的状态,也不能控制计算任务何时结束。" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": 4, 32 | "metadata": {}, 33 | "outputs": [ 34 | { 35 | "name": "stdout", 36 | "output_type": "stream", 37 | "text": [ 38 | "Scheduled for https://www.baidu.com/: \n", 39 | "Scheduled for https://www.google.com/: \n", 40 | "Scheduled for https://twitter.com/: \n", 41 | " result: https://www.baidu.com/\n", 42 | " result: https://www.google.com/\n", 43 | " result: https://twitter.com/\n" 44 | ] 45 | }, 46 | { 47 | "data": { 48 | "text/plain": [ 49 | "['https://www.baidu.com/', 'https://www.google.com/', 'https://twitter.com/']" 50 | ] 51 | }, 52 | "execution_count": 4, 53 | "metadata": {}, 54 | "output_type": "execute_result" 55 | } 56 | ], 57 | "source": [ 58 | "# 简单的期物用法\n", 59 | "import time\n", 60 | "from concurrent import futures\n", 61 | "\n", 62 | "\n", 63 | "def fake_download(url):\n", 64 | " time.sleep(1) # 这里用的是多线程,所以可以直接考虑 sleep\n", 65 | " return url\n", 66 | "\n", 67 | "\n", 68 | "def download_many(url_list):\n", 69 | " with futures.ThreadPoolExecutor(max_workers=2) as executor:\n", 70 | " to_do = []\n", 71 | " for url in url_list:\n", 72 | " future = executor.submit(fake_download, url)\n", 73 | " to_do.append(future)\n", 74 | " print(f\"Scheduled for {url}: {future}\") # 因为 worker 数量有限,所以会有一个 future 处于 pending 状态\n", 75 | " results = []\n", 76 | " for future in futures.as_completed(to_do):\n", 77 | " result = future.result()\n", 78 | " print(f'{future} result: {result}')\n", 79 | " results.append(result)\n", 80 | " return results\n", 81 | "\n", 82 | "download_many([\"https://www.baidu.com/\", \"https://www.google.com/\",\n", 83 | " \"https://twitter.com/\"])" 84 | ] 85 | }, 86 | { 87 | "cell_type": "markdown", 88 | "metadata": {}, 89 | "source": [ 90 | "`ThreadExecutor` 使用多线程处理并发。在程序被 IO 阻塞时,Python 标准库会释放 GIL,以允许其它线程运行。 \n", 91 | "所以,GIL 的存在并不会对 IO 密集型多线程并发造成太大影响。\n", 92 | "\n", 93 | "\n", 94 | "`concurrent` 包中提供了 `ThreadPoolExecutor` 和 `ProcessPoolExecutor` 类,分别对应多线程和多进程模型。 \n", 95 | "关于两种模型的使用及推荐并发数,我们有一个经验:\n", 96 | "* CPU 密集型任务,推荐使用多进程模型,以利用 CPU 的多个核心,`max_workers` 应设置为 CPU 核数;\n", 97 | "* IO 密集型任务,多核 CPU 不会提高性能,所以推荐使用多线程模型,可以省下多进程带来的资源开销,`max_workers` 可以尽可能设置多一些。" 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": 10, 103 | "metadata": {}, 104 | "outputs": [ 105 | { 106 | "name": "stdout", 107 | "output_type": "stream", 108 | "text": [ 109 | "[15:36:08] Done with 0\n", 110 | "[15:36:08] Done with 1\n", 111 | "[15:36:08] Done with 2\n", 112 | "[15:36:09] Done with 3\n", 113 | "[15:36:09] Done with 4\n", 114 | "Results: [0, 1, 2, 3, 4]\n" 115 | ] 116 | } 117 | ], 118 | "source": [ 119 | "# Executor.map\n", 120 | "# 并发运行多个可调用的对象时,可以使用 map 方法\n", 121 | "\n", 122 | "import time\n", 123 | "from concurrent import futures\n", 124 | "\n", 125 | "\n", 126 | "def fake_download(url):\n", 127 | " time.sleep(1) # 这里用的是多线程,所以可以直接考虑 sleep\n", 128 | " print(f'[{time.strftime(\"%H:%M:%S\")}] Done with {url}\\n', end='')\n", 129 | " return url\n", 130 | "\n", 131 | "\n", 132 | "def download_many(url_list):\n", 133 | " with futures.ThreadPoolExecutor(max_workers=3) as executor:\n", 134 | " results = executor.map(fake_download, url_list)\n", 135 | " return results\n", 136 | "\n", 137 | "results = download_many(list(range(5)))\n", 138 | "print('Results:', list(results))" 139 | ] 140 | }, 141 | { 142 | "cell_type": "markdown", 143 | "metadata": {}, 144 | "source": [ 145 | "`map` 的使用可能更方便一点,但 `futures.as_completed` 则更灵活:支持不同的运算方法及参数,甚至支持来自不同 `Executor` 的 `future`." 146 | ] 147 | }, 148 | { 149 | "cell_type": "markdown", 150 | "metadata": {}, 151 | "source": [ 152 | "## 总结\n", 153 | "15 年的时候看过一篇文章叫[《一行 Python 实现并行化》](https://segmentfault.com/a/1190000000414339),里面讲述了如何利用 `multiprocessing.Pool.map`(或者 `multiprocessing.dummy.Pool.map`)快速实现多进程 / 多线程模型的并发任务处理。\n", 154 | "\n", 155 | "[`concurrent.furures`](https://docs.python.org/3/library/concurrent.futures.html) 模块于 Python 3.2 版本引入,它把线程、进程和队列是做服务的基础设施,无须手动进行管理即可轻松实现并发任务。同时,这个包引入了“期物”的概念,可以对并发任务更加规范地进行注册及管理。" 156 | ] 157 | } 158 | ], 159 | "metadata": { 160 | "kernelspec": { 161 | "display_name": "Python 3", 162 | "language": "python", 163 | "name": "python3" 164 | }, 165 | "language_info": { 166 | "codemirror_mode": { 167 | "name": "ipython", 168 | "version": 3 169 | }, 170 | "file_extension": ".py", 171 | "mimetype": "text/x-python", 172 | "name": "python", 173 | "nbconvert_exporter": "python", 174 | "pygments_lexer": "ipython3", 175 | "version": "3.7.0" 176 | } 177 | }, 178 | "nbformat": 4, 179 | "nbformat_minor": 2 180 | } 181 | -------------------------------------------------------------------------------- /第18章:使用asyncio包处理并发.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 使用 asyncio 包处理并发\n", 8 | "> 并发是指一次处理多件事。 \n", 9 | "> 并行是指一次做多件事。 \n", 10 | "> 二者不同,但是有联系。 \n", 11 | "> 一个关于结构,一个关于执行。 \n", 12 | "> 并发用于制定方案,用来解决可能(但未必)并行的问题。 \n", 13 | "> ——Rob Pike, Go 语言的创造者之一\n", 14 | "\n", 15 | "本章介绍 `asyncio` 包,这个包使用事件循环驱动的协程实现并发。 \n", 16 | "`asyncio` 包支持 Python 3.3 及以上版本,并在 Python 3.4 中正式成为标准库。\n", 17 | "\n", 18 | "本章讨论以下话题:\n", 19 | "* 对比一个简单的多线程程序和对应的 asyncio 版,说明多线程和异步任务之间的关系\n", 20 | "* asyncio.Future 类与 concurrent.futures.Future 类之间的区别\n", 21 | "* 第 17 章中下载国旗那些示例的异步版\n", 22 | "* 摒弃线程或进程,如何使用异步编程管理网络应用中的高并发\n", 23 | "* 在异步编程中,与回调相比,协程显著提升性能的方式\n", 24 | "* 如何把阻塞的操作交给线程池处理,从而避免阻塞事件循环\n", 25 | "* 使用 asyncio 编写服务器,重新审视 Web 应用对高并发的处理方式\n", 26 | "* 为什么 asyncio 已经准备好对 Python 生态系统产生重大影响\n", 27 | "\n", 28 | "由于 Python 3.5 支持了 `async` 和 `await`,所以书中的 ` @asyncio.coroutine` 和 `yield from` 均会被替换成 `async def` 和 `await`. \n", 29 | "同时,[官方 Repo](https://github.com/fluentpython/example-code/tree/master/18b-async-await) 也使用 `async` 和 `await` 重新实现了书中的实例。" 30 | ] 31 | }, 32 | { 33 | "cell_type": "markdown", 34 | "metadata": {}, 35 | "source": [ 36 | "## 协程的优点\n", 37 | "多线程的代码,很容易在任务执行过程中被挂起;而协程的代码是手动挂起的,只要代码没有运行到 `await`,调度器就不会挂起这个协程。" 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": null, 43 | "metadata": {}, 44 | "outputs": [], 45 | "source": [ 46 | "# 基于 asyncio 的协程并发实现\n", 47 | "# https://github.com/fluentpython/example-code/blob/master/18b-async-await/spinner_await.py\n", 48 | "# 在 Jupyter notebook 中运行该代码会报错,所以考虑把它复制出去单独运行。\n", 49 | "# 见 https://github.com/jupyter/notebook/issues/3397\n", 50 | "import asyncio\n", 51 | "import itertools\n", 52 | "import sys\n", 53 | "\n", 54 | "\n", 55 | "async def spin(msg):\n", 56 | " write, flush = sys.stdout.write, sys.stdout.flush\n", 57 | " for char in itertools.cycle('|/-\\\\'):\n", 58 | " status = char + ' ' + msg\n", 59 | " write(status)\n", 60 | " flush()\n", 61 | " write('\\x08' * len(status))\n", 62 | " try:\n", 63 | " await asyncio.sleep(.1)\n", 64 | " except asyncio.CancelledError:\n", 65 | " break\n", 66 | " write(' ' * len(status) + '\\x08' * len(status)) # 使用空格和 '\\r' 清空本行\n", 67 | "\n", 68 | "\n", 69 | "async def slow_function():\n", 70 | " # pretend waiting a long time for I/O\n", 71 | " await asyncio.sleep(3)\n", 72 | " return 42\n", 73 | "\n", 74 | "\n", 75 | "async def supervisor():\n", 76 | " spinner = asyncio.ensure_future(spin('thinking!')) # 执行它,但不需要等待它返回结果\n", 77 | " print('spinner object:', spinner)\n", 78 | " result = await slow_function() # 挂起当前协程,等待 slow_function 完成\n", 79 | " spinner.cancel() # 从 await 中抛出 CancelledError,终止协程\n", 80 | " return result\n", 81 | "\n", 82 | "\n", 83 | "def main():\n", 84 | " loop = asyncio.get_event_loop()\n", 85 | " result = loop.run_until_complete(supervisor())\n", 86 | " loop.close()\n", 87 | " print('Answer:', result)\n", 88 | "\n", 89 | "\n", 90 | "main()" 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "metadata": {}, 96 | "source": [ 97 | "## 基于协程的并发编程用法\n", 98 | "定义协程:\n", 99 | "1. 使用 `async def` 定义协程任务;\n", 100 | "2. 在协程中使用 `await` 挂起当前协程,唤起另一个协程,并等待它返回结果;\n", 101 | "3. 处理完毕后,使用 `return` 返回当前协程的结果\n", 102 | "\n", 103 | "运行协程:不要使用 `next` 或 `.send()` 来操控协程,而是把它交给 `event_loop` 去完成。\n", 104 | "```python\n", 105 | "loop = asyncio.get_event_loop()\n", 106 | "result = loop.run_until_complete(supervisor())\n", 107 | "```\n", 108 | "\n", 109 | "注:`@asyncio.coroutine` 并**不会预激协程**。" 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "metadata": {}, 115 | "source": [ 116 | "## 使用协程进行下载\n", 117 | "[aiohttp](https://aiohttp.readthedocs.io/en/stable/) 库提供了基于协程的 HTTP 请求功能。 \n", 118 | "书中提供的并行下载国旗的简单示例可以在[这里](https://github.com/fluentpython/example-code/blob/master/17-futures/countries/flags_await.py)看到。 \n", 119 | "完整示例在下载国旗的同时还下载了国家的元信息,并考虑了出错处理及并发数量,可以在[这里](https://github.com/fluentpython/example-code/blob/master/17-futures/countries/flags3_asyncio.py)看到。\n", 120 | "\n", 121 | "`asyncio.Semaphore` 提供了协程层面的[信号量](https://zh.wikipedia.org/zh-cn/%E4%BF%A1%E5%8F%B7%E9%87%8F)服务,我们可以使用这个信号量来[限制并发数](https://github.com/fluentpython/example-code/blob/master/17-futures/countries/flags3_asyncio.py#L61)。\n", 122 | "\n", 123 | "```python\n", 124 | "with (await semaphore): # semaphore.acquire\n", 125 | " image = await get_flag(base_url, cc)\n", 126 | " # semaphore.release\n", 127 | "```" 128 | ] 129 | }, 130 | { 131 | "cell_type": "markdown", 132 | "metadata": {}, 133 | "source": [ 134 | "## 在协程中避免阻塞任务\n", 135 | "文件 IO 是一个非常耗时的操作,但 asyncio 并没有提供基于协程的文件操作,所以我们可以在协程中使用 `run_in_executor` 将任务交给 `Executor` 去执行[异步操作](https://github.com/fluentpython/example-code/blob/master/17-futures/countries/flags3_asyncio.py#L74)。\n", 136 | "\n", 137 | "注:[aiofiles](https://github.com/Tinche/aiofiles) 实现了基于协程的文件操作。" 138 | ] 139 | }, 140 | { 141 | "cell_type": "markdown", 142 | "metadata": {}, 143 | "source": [ 144 | "## 使用 aiohttp 编写 Web 服务器\n", 145 | "廖雪峰写了个关于 asyncio 的[小教程](https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014320981492785ba33cc96c524223b2ea4e444077708d000)。 \n", 146 | "如果需要实现大一点的应用,可以考虑使用 [Sanic](https://sanic.readthedocs.io/en/latest/) 框架。基于这个框架的 Web 应用写法和 Flask 非常相似。" 147 | ] 148 | }, 149 | { 150 | "cell_type": "markdown", 151 | "metadata": {}, 152 | "source": [ 153 | "## 使用 asyncio 做很多很多事情\n", 154 | "GitHub 的 [aio-libs](https://github.com/aio-libs) 组织提供了很多异步驱动,比如 MySQL, Zipkin 等。" 155 | ] 156 | } 157 | ], 158 | "metadata": { 159 | "kernelspec": { 160 | "display_name": "Python 3", 161 | "language": "python", 162 | "name": "python3" 163 | }, 164 | "language_info": { 165 | "codemirror_mode": { 166 | "name": "ipython", 167 | "version": 3 168 | }, 169 | "file_extension": ".py", 170 | "mimetype": "text/x-python", 171 | "name": "python", 172 | "nbconvert_exporter": "python", 173 | "pygments_lexer": "ipython3", 174 | "version": "3.7.0" 175 | } 176 | }, 177 | "nbformat": 4, 178 | "nbformat_minor": 2 179 | } 180 | -------------------------------------------------------------------------------- /第19章:动态属性和特性.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 动态属性和特性\n", 8 | "> 特性至关重要的地方在于,特性的存在使得开发者可以非常安全并且确定可行地将公共数据属性作为类的公共接口的一部分开放出来。\n", 9 | "> ——Alex Martelli, Python 贡献者和图书作者\n", 10 | "\n", 11 | "本章内容:\n", 12 | "* 特性(property)\n", 13 | "* 动态属性存取(`__getattr__` 和 `__setattr__`)\n", 14 | "* 对象的动态创建(`__new__`)" 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "## 特性\n", 22 | "Python 特性(property)可以使我们在不改变接口的前提下,使用**存取方法**修改数据属性。" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 3, 28 | "metadata": {}, 29 | "outputs": [ 30 | { 31 | "name": "stdout", 32 | "output_type": "stream", 33 | "text": [ 34 | "Set val 1\n", 35 | "Val deleted!\n" 36 | ] 37 | } 38 | ], 39 | "source": [ 40 | "# property\n", 41 | "class A:\n", 42 | " def __init__(self):\n", 43 | " self._val = 0\n", 44 | " \n", 45 | " @property\n", 46 | " def val(self):\n", 47 | " return self._val\n", 48 | "\n", 49 | " @val.setter\n", 50 | " def val(self, val):\n", 51 | " print('Set val', val)\n", 52 | " self._val = val\n", 53 | " \n", 54 | " @val.deleter\n", 55 | " def val(self):\n", 56 | " print('Val deleted!')\n", 57 | "\n", 58 | "a = A()\n", 59 | "assert a.val == 0\n", 60 | "a.val = 1\n", 61 | "assert a.val == 1\n", 62 | "del a.val\n", 63 | "assert a.val == 1 # del val 只是触发了 deleter 方法,再取值时还会执行 val 的 getter 函数" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "metadata": {}, 69 | "source": [ 70 | "## 动态属性\n", 71 | "当访问对象的某个属性时,如果对象没有这个属性,解释器会尝试调用对象的 `__attr__` 方法。 \n", 72 | "但是注意,这个属性名必须是一个合法的标识符。\n", 73 | "\n", 74 | "注:`__getattr__` 和 `__getattribute__` 的区别在于,`__getattribute__` 在每次访问对象属性时都会触发,而 `__getattr__` 只在该对象没有这个属性的时候才会触发。" 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": 10, 80 | "metadata": {}, 81 | "outputs": [ 82 | { 83 | "name": "stdout", 84 | "output_type": "stream", 85 | "text": [ 86 | "getattribute a\n", 87 | "getattribute b\n", 88 | "getattr b\n", 89 | "1 b\n" 90 | ] 91 | } 92 | ], 93 | "source": [ 94 | "class B:\n", 95 | " a = 1\n", 96 | " def __getattr__(self, attr):\n", 97 | " print('getattr', attr)\n", 98 | " return attr\n", 99 | "\n", 100 | " def __getattribute__(self, attr):\n", 101 | " print('getattribute', attr)\n", 102 | " return super().__getattribute__(attr)\n", 103 | "\n", 104 | "b = B()\n", 105 | "print(b.a, b.b)" 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "metadata": {}, 111 | "source": [ 112 | "## __new__ 方法\n", 113 | "`__new__` 方法是类上的一个特殊方法,用于生成一个新对象。 \n", 114 | "与 `__init__` 不同,`__new__` 方法必须要返回一个对象,而 `__init__` 则不需要。 \n", 115 | "调用 `A.__new__` 时,返回的对象不一定需要是 A 类的实例。" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": 17, 121 | "metadata": {}, 122 | "outputs": [ 123 | { 124 | "name": "stdout", 125 | "output_type": "stream", 126 | "text": [ 127 | "arg bar\n", 128 | "arg bar\n" 129 | ] 130 | } 131 | ], 132 | "source": [ 133 | "# 对象构造过程示意\n", 134 | "class Foo:\n", 135 | " # __new__ 是一个特殊方法,所以不需要 @classmethod 装饰器\n", 136 | " def __new__(cls, arg):\n", 137 | " if arg is None:\n", 138 | " return []\n", 139 | " return super().__new__(cls) # 用 object.__new__ 生成对象后开始执行 __init__ 函数\n", 140 | "\n", 141 | " def __init__(self, arg):\n", 142 | " print('arg:', arg)\n", 143 | " self.arg = arg\n", 144 | "\n", 145 | "\n", 146 | "def object_maker(the_class, some_arg):\n", 147 | " new_object = the_class.__new__(the_class, some_arg)\n", 148 | " if isinstance(new_object, the_class):\n", 149 | " the_class.__init__(new_object, some_arg)\n", 150 | " return new_object \n", 151 | " \n", 152 | "# 下述两个语句的作用基本等效\n", 153 | "x = Foo('bar')\n", 154 | "y = object_maker(Foo, 'bar')\n", 155 | "assert x.arg == y.arg == 'bar'\n", 156 | "n = Foo(None)\n", 157 | "assert n == []" 158 | ] 159 | }, 160 | { 161 | "cell_type": "markdown", 162 | "metadata": {}, 163 | "source": [ 164 | "## 杂谈\n", 165 | "[shelve](https://docs.python.org/3/library/shelve.html) 是 Python 自带的、类 `dict` 的 KV 数据库,支持持久化存储。\n", 166 | "\n", 167 | "```python\n", 168 | "import shelve\n", 169 | "\n", 170 | "d = shelve.open(filename) # open -- file may get suffix added by low-level\n", 171 | " # library\n", 172 | "\n", 173 | "d[key] = data # store data at key (overwrites old data if\n", 174 | " # using an existing key)\n", 175 | "data = d[key] # retrieve a COPY of data at key (raise KeyError\n", 176 | " # if no such key)\n", 177 | "del d[key] # delete data stored at key (raises KeyError\n", 178 | " # if no such key)\n", 179 | "\n", 180 | "flag = key in d # true if the key exists\n", 181 | "klist = list(d.keys()) # a list of all existing keys (slow!)\n", 182 | "\n", 183 | "# as d was opened WITHOUT writeback=True, beware:\n", 184 | "d['xx'] = [0, 1, 2] # this works as expected, but...\n", 185 | "d['xx'].append(3) # *this doesn't!* -- d['xx'] is STILL [0, 1, 2]!\n", 186 | "\n", 187 | "# having opened d without writeback=True, you need to code carefully:\n", 188 | "temp = d['xx'] # extracts the copy\n", 189 | "temp.append(5) # mutates the copy\n", 190 | "d['xx'] = temp # stores the copy right back, to persist it\n", 191 | "\n", 192 | "# or, d=shelve.open(filename,writeback=True) would let you just code\n", 193 | "# d['xx'].append(5) and have it work as expected, BUT it would also\n", 194 | "# consume more memory and make the d.close() operation slower.\n", 195 | "\n", 196 | "d.close() # close it\n", 197 | "```\n", 198 | "架子(shelve)上放一堆泡菜(pickle)坛子…没毛病。" 199 | ] 200 | } 201 | ], 202 | "metadata": { 203 | "kernelspec": { 204 | "display_name": "Python 3", 205 | "language": "python", 206 | "name": "python3" 207 | }, 208 | "language_info": { 209 | "codemirror_mode": { 210 | "name": "ipython", 211 | "version": 3 212 | }, 213 | "file_extension": ".py", 214 | "mimetype": "text/x-python", 215 | "name": "python", 216 | "nbconvert_exporter": "python", 217 | "pygments_lexer": "ipython3", 218 | "version": "3.7.0" 219 | } 220 | }, 221 | "nbformat": 4, 222 | "nbformat_minor": 2 223 | } 224 | -------------------------------------------------------------------------------- /第20章:属性描述符.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 属性描述符\n", 8 | "> 学会描述符之后,不仅有更多的工具集可用,还会对 Python 的运作方式有更深入的理解,并由衷赞叹 Python 设计的优雅。 \n", 9 | "> ——Raymond Hettinger, Python 核心开发者和专家\n", 10 | "\n", 11 | "本章的话题是描述符。 \n", 12 | "描述符是实现了特定协议的类,这个协议包括 `__get__`、`__set__`、和 `__delete__` 方法。\n", 13 | "\n", 14 | "有了它,我们就可以在类上定义一个托管属性,并把所有对实例中托管属性的读写操作交给描述符类去处理。" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 3, 20 | "metadata": {}, 21 | "outputs": [ 22 | { 23 | "name": "stdout", 24 | "output_type": "stream", 25 | "text": [ 26 | "__get__ None \n", 27 | "<__main__.CharField object at 0x063AF1F0>\n", 28 | "__get__ <__main__.SomeModel object at 0x063AF4B0> \n", 29 | "some name\n", 30 | "__set__ <__main__.SomeModel object at 0x063AF4B0> another name\n", 31 | "__get__ <__main__.SomeModel object at 0x063AF4B0> \n", 32 | "another name\n", 33 | "__set__ <__main__.SomeModel object at 0x063AF4B0> 1\n", 34 | "TypeError('Value should be string')\n" 35 | ] 36 | } 37 | ], 38 | "source": [ 39 | "# 描述符示例:将一个属性托管给一个描述符类\n", 40 | "class CharField: # 描述符类\n", 41 | " def __init__(self, field_name):\n", 42 | " self.field_name = field_name\n", 43 | "\n", 44 | " def __get__(self, instance, storage_cls):\n", 45 | " print('__get__', instance, storage_cls)\n", 46 | " if instance is None: # 直接在类上访问托管属性时,instance 为 None\n", 47 | " return self\n", 48 | " return instance[self.field_name]\n", 49 | "\n", 50 | " def __set__(self, instance, value):\n", 51 | " print('__set__', instance, value)\n", 52 | " if not isinstance(value, str):\n", 53 | " raise TypeError('Value should be string')\n", 54 | " instance[self.field_name] = value\n", 55 | "\n", 56 | "\n", 57 | "class SomeModel: # 托管类\n", 58 | " name = CharField('name') # 描述符实例,也是托管类中的托管属性\n", 59 | "\n", 60 | " def __init__(self, **kwargs):\n", 61 | " self._dict = kwargs # 出巡属性,用于存储属性\n", 62 | "\n", 63 | " def __getitem__(self, item):\n", 64 | " return self._dict[item]\n", 65 | "\n", 66 | " def __setitem__(self, item, value):\n", 67 | " self._dict[item] = value\n", 68 | "\n", 69 | "\n", 70 | "\n", 71 | "print(SomeModel.name)\n", 72 | "d = SomeModel(name='some name')\n", 73 | "print(d.name)\n", 74 | "d.name = 'another name'\n", 75 | "print(d.name)\n", 76 | "try:\n", 77 | " d.name = 1\n", 78 | "except Exception as e:\n", 79 | " print(repr(e))" 80 | ] 81 | }, 82 | { 83 | "cell_type": "markdown", 84 | "metadata": {}, 85 | "source": [ 86 | "## 描述符的种类\n", 87 | "根据描述符上实现的方法类型,我们可以把描述符分为**覆盖型描述符**和**非覆盖型描述符**。\n", 88 | "\n", 89 | "实现 `__set__` 方法的描述符属于覆盖型描述符,因为虽然描述符是类属性,但是实现 `__set__` 方法的话,会覆盖对实例属性的赋值操作。 \n", 90 | "而没有实现 `__set__` 方法的描述符是非覆盖型描述符。对实例的托管属性赋值,则会覆盖掉原有的描述符属性,此后再访问该属性时,将不会触发描述符的 `__get__` 操作。如果想恢复原有的描述符行为,则需要用 `del` 把覆盖掉的属性删除。\n", 91 | "\n", 92 | "具体可以看[官方 Repo 的例子](https://github.com/fluentpython/example-code/blob/master/20-descriptor/descriptorkinds.py)。" 93 | ] 94 | }, 95 | { 96 | "cell_type": "markdown", 97 | "metadata": {}, 98 | "source": [ 99 | "## 描述符的用法建议\n", 100 | "* 如果只想实现一个只读描述符,可以考虑使用 `property` 而不是自己去实现描述符;\n", 101 | "* 只读描述符必须有 `__set__` 方法,在方法内抛出 `AttributeError`,防止属性在写时被覆盖;\n", 102 | "* 用于验证的描述符可以只有 `__set__` 方法:通过验证后,可以修改 `self.__dict__[key]` 来将属性写入对象;\n", 103 | "* 仅有 `__get__` 方法的描述符可以实现高效缓存;\n", 104 | "* 非特殊的方法可以被实例属性覆盖。" 105 | ] 106 | } 107 | ], 108 | "metadata": { 109 | "kernelspec": { 110 | "display_name": "Python 3", 111 | "language": "python", 112 | "name": "python3" 113 | }, 114 | "language_info": { 115 | "codemirror_mode": { 116 | "name": "ipython", 117 | "version": 3 118 | }, 119 | "file_extension": ".py", 120 | "mimetype": "text/x-python", 121 | "name": "python", 122 | "nbconvert_exporter": "python", 123 | "pygments_lexer": "ipython3", 124 | "version": "3.7.0" 125 | } 126 | }, 127 | "nbformat": 4, 128 | "nbformat_minor": 2 129 | } 130 | -------------------------------------------------------------------------------- /第21章:类元编程.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 类元编程\n", 8 | "> (元类)是深奥的知识,99% 的用户都无需关注。如果你想知道是否需要使用元类,我告诉你,不需要(真正需要使用元类的人确信他们需要,无需解释原因)。\n", 9 | "> ——Tim Peters, Timsort 算法的发明者,活跃的 Python 贡献者\n", 10 | "\n", 11 | "元类功能强大,但是难以掌握。使用元类可以创建具有某种特质的全新类中,例如我们见过的抽象基类。\n", 12 | "\n", 13 | "本章还会谈及导入时和运行时的区别。\n", 14 | "\n", 15 | "注:除非开发框架,否则不要编写元类。" 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "metadata": {}, 21 | "source": [ 22 | "## 类工厂函数\n", 23 | "像 `collections.namedtuple` 一样,类工厂函数的返回值是一个类,这个类的特性(如属性名等)可能由函数参数提供。 \n", 24 | "可以参见[官方示例](https://github.com/fluentpython/example-code/blob/master/21-class-metaprog/factories.py)。 \n", 25 | "原理:使用 `type` 构造方法,可以构造一个新的类。" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [ 34 | "help(type)" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "metadata": {}, 40 | "source": [ 41 | "## 元类\n", 42 | "元类是一个类,但因为它继承自 `type`,所以可以通过它生成一个类。\n", 43 | "\n", 44 | "在使用元类时,将会调用元类的 `__new__` 和 `__init__`,为类添加更多特性。 \n", 45 | "这一步会在**导入**时完成,而不是在类进行实例化时。\n", 46 | "\n", 47 | "元类的作用举例:\n", 48 | "* 验证属性\n", 49 | "* 一次性把某个/种装饰器依附到多个方法上(记得以前写过一个装饰器来实现这个功能,因为那个类的 `metaclass` 被占了)\n", 50 | "* 序列化对象或转换数据\n", 51 | "* 对象关系映射\n", 52 | "* 基于对象的持久存储\n", 53 | "* 动态转换使用其他语言编写的结构\n", 54 | "\n", 55 | "### Python 中各种类的关系\n", 56 | "> `object` 类和 `type` 类的关系很独特:`object` 是 `type` 的实例,而 `type` 是 `object` 的子类。\n", 57 | "所有类都直接或间接地是 `type` 的实例,不过只有元类同时也是 `type` 的子类,因为**元类从 `type` 类继承了构造类的能力**。\n", 58 | "\n", 59 | "这里面的关系比较复杂,简单理一下\n", 60 | "* 实例关系\n", 61 | " * `type` 可以产出类,所以 `type` 的实例是类(`isinstance(int, type)`);\n", 62 | " * 元类继承自 `type` 类,所以元类也具有生成类实例的**能力**(`isinstance(Sequence, ABCMeta)`)\n", 63 | "* 继承关系\n", 64 | " * Python 中一切皆对象,所以所有类都是 `object` 的子类(`object in int.__mro__`)\n", 65 | " * 元类要**继承**自 `type` 对象,所以元类是 `type` 的子类(`type in ABCMeta.__mro__`)" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": 6, 71 | "metadata": {}, 72 | "outputs": [ 73 | { 74 | "name": "stdout", 75 | "output_type": "stream", 76 | "text": [ 77 | "Enter SomeClass\n", 78 | "Metaclass __init__\n", 79 | "{'val': 2} {'__module__': '__main__', '__init__': , '__dict__': , '__weakref__': , '__doc__': None, 'a': 1}\n" 80 | ] 81 | } 82 | ], 83 | "source": [ 84 | "# 构建一个元类\n", 85 | "class SomeMeta(type):\n", 86 | " def __init__(cls, name, bases, dic):\n", 87 | " # 这里 __init__ 的是 SomeClass,因为它是个类,所以我们直接用 cls 而不是 self 来命名它\n", 88 | " print('Metaclass __init__')\n", 89 | " # 为我们的类添加一个**类**属性\n", 90 | " cls.a = 1\n", 91 | "\n", 92 | "class SomeClass(metaclass=SomeMeta):\n", 93 | " # 在解释器编译这个类的最后,SomeMeta 的 __init__ 将被调用\n", 94 | " print('Enter SomeClass')\n", 95 | " def __init__(self, val):\n", 96 | " # 这个函数在 SomeClass 实例化时才会被调用\n", 97 | " self.val = val\n", 98 | "\n", 99 | " \n", 100 | "assert SomeClass.a == 1 # 元类为类添加的类属性\n", 101 | "sc = SomeClass(2)\n", 102 | "assert sc.val == 2\n", 103 | "assert sc.a == 1\n", 104 | "print(sc.__dict__, SomeClass.__dict__)" 105 | ] 106 | }, 107 | { 108 | "cell_type": "markdown", 109 | "metadata": {}, 110 | "source": [ 111 | "关于类构造过程,可以参见官方 Repo 中的[代码执行练习(evaltime)部分](https://github.com/fluentpython/example-code/tree/master/21-class-metaprog)。" 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": 16, 117 | "metadata": {}, 118 | "outputs": [ 119 | { 120 | "name": "stdout", 121 | "output_type": "stream", 122 | "text": [ 123 | "{'name': 'Name', 'title': 'Title'}\n", 124 | "['Name', 'Title']\n" 125 | ] 126 | } 127 | ], 128 | "source": [ 129 | "# 用元类构造描述符\n", 130 | "from collections import OrderedDict\n", 131 | "\n", 132 | "\n", 133 | "class Field:\n", 134 | " def __get__(self, instance, cls):\n", 135 | " if instance is None:\n", 136 | " return self\n", 137 | " name = self.__name__\n", 138 | " return instance._value_dict.get(name)\n", 139 | "\n", 140 | " def __set__(self, instance, val):\n", 141 | " name = self.__name__ # 通过 _entity_name 属性拿到该字段的名称\n", 142 | " instance._value_dict[name] = val\n", 143 | "\n", 144 | " \n", 145 | "class DesNameMeta(type):\n", 146 | " @classmethod\n", 147 | " def __prepare__(cls, name, bases):\n", 148 | " \"\"\"\n", 149 | " Python 3 特有的方法,用于返回一个映射对象\n", 150 | " 然后传给 __init__ 的 dic 参数\n", 151 | " \"\"\"\n", 152 | " return OrderedDict()\n", 153 | "\n", 154 | " def __init__(cls, name, bases, dic):\n", 155 | " field_names = []\n", 156 | " for name, val in dic.items():\n", 157 | " if isinstance(val, Field):\n", 158 | " val.__name__ = name # 在生成类的时候,将属性名加到了描述符中\n", 159 | " field_names.append(name)\n", 160 | " cls._field_names = field_names\n", 161 | " \n", 162 | "\n", 163 | "\n", 164 | "class NewDesNameMeta(type): # 使用 __new__ 方法构造新类\n", 165 | " def __new__(cls, name, bases, dic):\n", 166 | " for name, val in dic.items():\n", 167 | " if isinstance(val, Field):\n", 168 | " val.__name__ = name\n", 169 | " return super().__new__(cls, name, bases, dic)\n", 170 | "\n", 171 | "\n", 172 | "class SomeClass(metaclass=DesNameMeta):\n", 173 | " name = Field()\n", 174 | " title = Field()\n", 175 | " \n", 176 | " def __init__(self):\n", 177 | " self._value_dict = {}\n", 178 | " \n", 179 | " def __iter__(self):\n", 180 | " \"\"\"\n", 181 | " 按定义顺序输出属性值\n", 182 | " \"\"\"\n", 183 | " for field in self._field_names:\n", 184 | " yield getattr(self, field)\n", 185 | "\n", 186 | "\n", 187 | "assert SomeClass.name.__name__ == 'name'\n", 188 | "sc = SomeClass()\n", 189 | "sc.name = 'Name'\n", 190 | "sc.title = 'Title'\n", 191 | "assert sc.name == 'Name'\n", 192 | "print(sc._value_dict)\n", 193 | "print(list(sc))" 194 | ] 195 | }, 196 | { 197 | "cell_type": "markdown", 198 | "metadata": {}, 199 | "source": [ 200 | "上面的例子只是演示作用,实际上在设计框架的时候,`SomeClass` 会设计为一个基类(`models.Model`),框架用户只要继承自 `Model` 即可正常使用 `Field` 中的属性名,而无须知道 `DesNameMeta` 的存在。" 201 | ] 202 | }, 203 | { 204 | "cell_type": "markdown", 205 | "metadata": {}, 206 | "source": [ 207 | "## 类的一些特殊属性\n", 208 | "类上有一些特殊属性,这些属性不会在 `dir()` 中被列出,访问它们可以获得类的一些元信息。 \n", 209 | "同时,元类可以更改这些属性,以定制类的某些行为。\n", 210 | "\n", 211 | "* `cls.__bases__` \n", 212 | " 由类的基类组成的元组\n", 213 | "* `cls.__qualname__` \n", 214 | " 类或函数的限定名称,即从模块的全局作用域到类的点分路径\n", 215 | "* `cls.__subclasses__()` \n", 216 | " 这个方法返回一个列表,包含类的**直接**子类\n", 217 | "* `cls.__mro__` \n", 218 | " 类的方法解析顺序,这个属性是只读的,元类无法进行修改\n", 219 | "* `cls.mro()` \n", 220 | " 构建类时,如果需要获取储存在类属性 __mro__ 中的超类元组,解释器会调用这个方法。元类可以覆盖这个方法,定制要构建的类解析方法的顺序。" 221 | ] 222 | }, 223 | { 224 | "cell_type": "markdown", 225 | "metadata": {}, 226 | "source": [ 227 | "## 延伸阅读\n", 228 | "[`types.new_class`](https://docs.python.org/3/library/types.html#types.new_class) 和 `types.prepare_class` 可以辅助我们进行类元编程。\n", 229 | "\n", 230 | "最后附上一个名言警句:\n", 231 | "\n", 232 | "> 此外,不要在生产代码中定义抽象基类(或元类)……如果你很想这样做,我打赌可能是因为你想“找茬”,刚拿到新工具的人都有大干一场的冲动。如果你能避开这些深奥的概念,你(以及未来的代码维护者)的生活将更愉快,因为代码简洁明了。 \n", 233 | "> ——Alex Martelli" 234 | ] 235 | } 236 | ], 237 | "metadata": { 238 | "kernelspec": { 239 | "display_name": "Python 3", 240 | "language": "python", 241 | "name": "python3" 242 | }, 243 | "language_info": { 244 | "codemirror_mode": { 245 | "name": "ipython", 246 | "version": 3 247 | }, 248 | "file_extension": ".py", 249 | "mimetype": "text/x-python", 250 | "name": "python", 251 | "nbconvert_exporter": "python", 252 | "pygments_lexer": "ipython3", 253 | "version": "3.7.0" 254 | } 255 | }, 256 | "nbformat": 4, 257 | "nbformat_minor": 2 258 | } 259 | --------------------------------------------------------------------------------