流畅的python
2、序列构成的数组
2.1、内置序列的类型
- 容器序列
- list, tuple, collections.deque
- 扁平序列
- str, bytes, bytearray, memoryview, array.array
或者
- 可变序列
- 不可变序列
- tuple, str, bytes
2.2、列表推导和生成器
- 原则
- 一般只用列表推导创建新的列表,并且尽量保持简短。
- 列表推导有自己的局部作用域,不会影响外面的变量。
2.2.4、生成器表达式
- 如果生成器是函数调用过程中的唯一参数,那么不需要额外的括号围起来。
2.3、元组
2.3.2、元组的拆包
- 元组拆包可以用到任何可迭代的对象上,被迭代的对象中的元素数量必须要跟接受这些元素的元组的空档一致。除非我们用*表示多余的元素。
- 可以用*运算符把一个可迭代的对象拆开作为函数的参数
2.3.3、嵌套元组拆包
2.3.4、具名元组
- collections.nametuple('Card', ['rank', 'suit'])
- 创建一个具元组需要两个参数,一个是类名,一个是类的各字段的名字。后者可以由数个字符串组成的可迭代对象,或者由空格分隔开的字段名组成的字符串。
2.4、切片
2.4.1、为什么会忽略最后一个元素
- 在切片和操作空间里不包含区间范围的最后一个元素是Python的风格,这个习惯编程语言以0作为下标的传统,好处:
- 可以迅速看出切片里有几个元素
- 可以用任意一个下标把序列分割成不重叠的部分
2.4.2、对对象进行切片
- s[a:b:c]
2.4.4、
- 如果复制的对象是一个切片,那么复制语句的右侧必须是个可迭代的对象,即便只有单独一个值,也要转化成可迭代的序列。
2.5、对序列使用 + 和 *
在拼接的过程中,两个被操作的序列都不会被修改,Python会新建一个包含同样数据类型数据的序列来作为拼接的结果。
坑:如果使用my_list=[[]] * 3 来初始化一个列表组成的列表,得到的列表包含的3个元素其实是3个引用,而且3个引用指向的都是同一个列表。
2.6、序列的增量赋值
- 可变对象:新元素追加到列表上
- 不可对象:新的对象被创建
- 坑:str是个例外,CPython对str做了优化,初始化内存的时候会流出额外的可扩展空间,进行增量操作的时候,不会涉及赋值原有字符串到新位置的操作。
2.7、list.sort
- 已排序的序列可以用来进行快速搜索,标准库 bisect 模块给我们提供了二分法查找计算。
2.9、当列表不是首选时
- 如果需要频繁的做先进先出的操作,deque的速度应该更快
- 查询一个元素是否出现在一个集合中,用set会更合适。
2.10、Numpy, Scipy
2.9.4、双向队列和其他形式的队列
- 双向队列
- collections
>>> from collections import deque
>>> dq = deque(range(10), maxlen=10) # 可选参数,表示容纳的数量
>>> dq.rotate(2) # 旋转操作
>>> dq.appendleft(-1) # 尾部添加元素,会挤掉开始的元素
>>> dq.extend([11,12,13]) # 列表内容逐渐添加
- queue Python标准库
- 线程间通信用的
- Queue
- LifoQueue
- PriorityQueue
- 接受正整数作为输入值,限制队列的大小。但是满员的时候不会扔掉旧元素腾出位置,而是会锁住,知道另外的线程移除了某个元素而腾出了位置。
- multiprocessing
- 实现了自己的Queue,是设计给进程间通信用的
- asyncio
- 包括Queue,LifoQueue,PriorityQueue和JoinableQueue,受上述两个模块的影响,但是为一步变成任务提供了专门的遍历。
- heapq
- 使用heapq建立堆的数据结构。
3、字典和集合
3.1、泛映射型
- 可散列
- 只有可散列的数据类型才能用作这些映射里的键(值不需要)
- 可散列的值在对象的生命周期中,他的散列值是不变的,需要实现
__hash__()
方法,还有有__qe__()
方法,才可以跟其他的键作比较。 - str, bytes, 数值类型都是可散列的。元组的话,只有当一个元组包含的所有元素都是可散列类型的情况下,才是可散列的。
3.2、字典推导
3.3、常见的映射方法
-
dict
-
collections defaultdict
-
collections OrderedDict
-
获取某个键对应的值时可以使用:
- d.get(key, default)
-
更新某个键对应的值的时候
- d.setdefault(key, default).append(data)
-
有时候为了方便起见,我们希望在通过这个键读取值的时候能得到一个默认值。有两个途径:一个是通过defaultdict类型,一个是自己定义dict的子类,然后实现
__missing__
方法
3.4.2、特殊方法 __missing__
-
__missing__
方法只会被__getitem__
调用,提供__missing__
方法对get或者__contains__
(in 运算符会用到这个方法)这些方法的使用没有影响。 - d[key]会调用
__getitem__
,如果key不存在,则会调用__missing__
方法。 - k in d 会调用
__contains__
方法。
3.5、字典的变种
-
collections.OrderedDict
-
在添加键的时候会保持顺序,popitem方法默认删除并返回的是字典里的最后一个元素
-
collections.ChainMap
-
容纳多个映射对象,在进行键查找操作的时候,会被当去哦一个整体被逐个查找,直到键被找到为止。
>>> from collections import ChainMap
>>> m = ChainMap({1:1}, {'a':1}, {'a':2})
>>> m['a']
>>> 2
-
collections.Counter
-
会给键准备一个整数计数器,每次更新键的时候会增加计数器。主要是用来统计可迭代对象元素出现的次数
-
collections.UserDict
-
把标准的dict用纯Python实现了一遍
3.6、子类化UserDict
- UserDict不是dict的子类,但是UserDict有一个叫做data的属性,是dict的实例。这个属性实际上是UserDict最终存储数据的地方。这样做的好处是可以避免在魔法方法中产生不必要的递归。
3.7、不可变映射类型
- types模块中引入了一个封装类叫MappingProxyType,可以用它来获取字典的只读实例
3.8、集合论
- 集合的本质是许多唯一对象的聚集,集合中的元素必须是可散列的
- 中缀运算符
-
|
返回的是合集 -
&
返回的是交集 -
-
返回的是差集
-
3.8.1、句法陷阱
- 创建一个空集,必须使用不带任何参数的构造方法。如果只是协程{}形式,跟以前一样,创建的实际上是个空字典。
- dis.dis 反汇编函数
>>> from dis import dis
>>> dis('{1}')
>>> dis('set([1])')
3.8.2、集合推导
in
<=
<
>=
>
3.9、dict和set的背后
- 如果在程序里有任何的磁盘输入/输出,那么不管查询多少个元素的字典或集合,所消耗的时间都能忽略不计。
- 散列值和相等性
- 内置的hash()方法可以用于所有的内置类型对象,如果是自定义的对象调用hash(),实际上运行的是自定义的
__hash__
- 内置的hash()方法可以用于所有的内置类型对象,如果是自定义的对象调用hash(),实际上运行的是自定义的
- 散列表算法
- 首先会调用hash来计算search_key的散列值,把这个值最低的几位数字当做偏移量,在散列表里查找表元。 如果表原始空的,则报出KeyError异常。如果search_key和found_key不匹配的话,称为散列冲突。
3.9.3、dict的实现和其导致的结果
- 键必须是可散列的
- 字典在内存上的开销巨大。
- 如果需要存放数量巨大的记录,放在由原则或是具名元组构成的列表中会是比较好的选择。最好不雅根据json风格。 其一是避免了散列表所消耗的空间,其二是无需把记录中字段的名字每个都存一遍。
- 键查询很快
3.9.4、set的实现以及导致的后果
- 集合里的元素必须是可散列的
- 集合很消耗内存
- 可以高效的判断元素是否存在于某个集合
- 元素的次序取决于被添加到的集合的次序
- 往集合里添加元素,可能会改变集合里已有元素的次序
4、文本和字节序列
4.1、字符问题
4.5、处理文本文件
- Unicode 三明治
- 尽早的把输入字节序列解码成字符串
- 尽量晚的把字符串编码成字节序列
- 内置的open函数会在读取文件的时候做必要的解码,写入文件时做必要的编码
- 需要在多台设备中或场合下运行的代码,一定不能依赖默认编码,打开文件时始终应该明确传入encoding=参数,因为不同设备使用的默认编码可能不同。
- 编码默认值 locale
- 文本文件默认使用locale.getpreferredencoding(),也是重定向到文件的 sys.stdout/stdin/stdeer的默认编码
5、一等函数
- 一等对象
- 运行时创建
- 能赋值给变量或数据结构中的元素
- 能作为参数传递给函数
- 能作为函数的返回结果
-
__doc__
是函数众多属性中的一个,用于生成对象的帮助文本
5.2、高阶函数
- 接受函数为参数或者把函数作为结果返回的函数是高阶函数。
- map
- sorted:参数key是函数。任何但灿函数都能作为key参数的值
- filter
- reduce
- reduce不再是内置函数,sum和reduce的通用思想是把某个操作连续应用到序列的元素上,累计之前的结果
- all(iterable)
- 如果每个元素都是真值,返回True。all([])返回True
- any(iterable)
- 只要iterable中有元素是真值,就返回True,any([])返回False
5.3、匿名函数
5.4、可调用对象
- 使用 def 或 lambda 表达式创建
- 内置函数
- 内置方法
- 方法
- 类
- 调用类时会运行类的
__new__
方法创建一个实例,然后运行__init__
方法初始化实例,最后把实例返回给调用方
- 调用类时会运行类的
- 类的实例
- 如果类定义了
__call__()
方法,那么它的实例可以作为函数调用。
- 如果类定义了
- 生成器函数
- 使用yield关键字的函数或方法,调用生成器函数返回的是生成器对象。
- 判断
- 使用内置的callable()函数判断对象是否调用。
- 实现
__call__
方法的类时创建函数类对象的简便方式。必须在内部维护一个状态,在调用之间可用。
5.6、函数内省
5.7、从定位参数到仅限关键字参数
- *args, **kwargs
- args是一个tuple,会承载函数中多余的position参数,
- kwargs是一个dict,会承载函数中多余的关键字参数,
- args后面的参数只能通过关键字传递,
- kwargs后面,不允许跟参数
5.8、函数对象有个__defaults__
属性,值是一个元组,里面保存着定位参数和关键字参数的默认值。仅限关键字参数的默认值在__kwdefaults__
属性中
6、使用一等函数实现设计模式
6.1、策略模式
- 定义一系列算法,把他们一一封装起来,并且使他们可以相互替换。本模式使得算法可以独立于使用它的客户而变化。
- Abstract Base Class 抽象基类 abc
- python3.4中抽象基类最简单的方法是子类化abc.ABC 。python3.0~3.3必须在 class语句中使用metaclass=关键字(如class Promotion(metaclass=ABCMeta))
6.1.4
- globals()
- 返回一个字典,表示当前的全局符号符。符号始终指向当前的模块
- 注意过滤处理,放置无限递归
- inspect.getmembers函数用于获取对象的属性。第二个是可选的判断条件,一个bool值。我们使用inspect.isfunction,只获取模块中的函数
6.2、命令模式
- 命令模式的目的是解耦调用操作的对象和提供实现的对象。
- 使用闭包在调用之间保存函数的内部状态。
- 实现单接口的类的实例替换成可调用对象,毕竟,每个Python可调用对象都实现了但方法的接口。
7、函数装饰器和闭包
- 闭包:装饰器中和回调式异步编程和函数式编程风格的基础
7.1、装饰器
- 装饰器是可调用的对象,参数是另一个函数(被装饰的函数)
- 装饰器只是语法糖
- 能把被装饰的函数替换成其他的函数
- 装饰器在加载模块时立即执行
7.2、python何时执行装饰器
- 在被装饰的函数定义后立即运行,通常在导入时
- 函数装饰器在导入模块时立刻执行,而被装饰的函数只在明确调用时运行
- 装饰器通常定义在一个模块中,然后应用到其他模块中的函数上。
- 大多数装饰器会在内部定义一个函数,然后将其返回
7.4、变量作用域规则
- 如果在函数赋值时想让解释器把b当做全局变量,要使用global声明
- dis模块提供了反汇编Python函数
7.5、闭包
-
__closure__
属性 -
__code__
属性- 表示编译后的函数定义体,保存局部变量和自由变量的名称
-
__co_varnames
:保存的局部变量 -
__co_freevars
:保存的自由变量
7.6、nonlocal声明
- 对于数字,字符串,元组等不可变类型,只能读取不能创新,如果尝试重新绑定,会隐世创建局部变量。
- nonlocal声明:把变量标记为自由变量,即使在函数中为变量赋予新值,也会是自由变量。如果是nonlocal声明的变量赋予新值,闭包中保存的绑定会更新。
- 装饰器:动态的给一个对象添加一些额外的职责
- functools.wraps装饰器把相关的属性从func复制到clocked中。
7.8、标准库中的装饰器
三个用于装饰方法的函数
- property
- classmethod
- staticmethod
7.8.1、使用functools.lru_cache
- Lease Recently used 缓存不会无限制增长,一段时间不用的缓存条目会被扔掉
- 除了优化递归算法之外
- lru_cache(maxsize=128, typed=False)
- maxsize参数指定存储多少个调用结果。存储,满了之后,旧的结果会被扔掉,为了达到最佳性能,maxsize应该为2的幂。
- typed参数,如果设为True,会把不同参数类型的结果分开保存,即把通常认为相等的浮点数和整数参数区分开。
- 因为lru_cache使用字典存储结果,而且键根据调用时传入的定位参数和关键字参数创建,所以被lru_cache装饰的函数,他的所有的参数都是可散列的。
7.8.2、functools.signaledispatch装饰器
- python不支持重载方法或函数,不能使用不同的签名定义htmlize的变体。
- 一种常见的做法是把htmlize编程一个分派函数,使用一串if/elif/elif调用专门的函数。但是这样不便于模块的用户扩展,显的笨拙。耦合度也比较高。
- Python3.4 functools.singledispatch装饰器
- 可以把整体方案拆分成多个模块,甚至可以为无法修改的类提供专门的函数。使用@singledispatch装饰的普通函数会变成泛函数。
- 根据第一个参数的类型,以不同方式执行相同操作的一组函数。
from functools import singledispatch import html @singledispatch def htmlize(obj): pass @htmlize.register(str): def _(text): pass
- @singledispatch标记处理object类型的基函数
- 各个专门函数使用@<base_function>.register(<type>)装饰
- 专门函数的名称无关紧要,_是个不错的选择,简单明了
- 可以叠放多个register装饰器,让同一个函数支持不同类型
7.9、叠放装饰器
@d1
@d2
def f():
print('f')
相当于f = d1(d2(f))
7.10、参数化装饰器
7.10.1、一个参数化的注册装饰器
- 从概念上看,这个新的register函数不是装饰器,而是装饰器工厂函数,调用他会返回真正的装饰器。
- 关键是 register()要返回decorate,然后把它应用到被装饰的函数上
- 结构上需要多一层嵌套
7.10.2、参数化clock装饰器
- 解释器最好通过实现
__call__
方法的类实现,不应该想本章的示例那样通过函数实现。 - 实现装饰器模式时最好使用类表示装饰器和要包装的组件
8、对象引用,可变性和垃圾回收
8.1、变量不是盒子
- 变量是盒子的比喻,有碍于理解面向对象语言中的引用式变量
- 对引用式变量来说,说把变量分配给对象更合理。对象在复制之前就创建了
- 因为变量只不过是标注,所有无法阻止为对象贴上多个标注。贴的标注,就是别名
8.2、标志,相等性和别名
- 每个对象都有标识、类型和值。对象一旦创建,他的标识绝不会变;你可以把标识理解为对象在内存中的地址。is运算符比较两个对象的标识,id函数返回对象标识的整数表示。
8.2.1、在==和is之间选择
- == 运算符比较两个对象的值
- is比较对象的标识
- 通常关注的是值,所以==出现的频率比较高
- 在变量和单例值之间比较时,应该使用is。检查变量绑定的值是不是None,推荐写法 x is None
8.2.2、元组的相对不可变性
- 元组与多数Python集合一样,保存的是对象的引用。如果引用的元素是可变的,即使元组本身不可变,元素依然可变。
- 元组的不可变性其实是值tuple数据结构的物理内容不可变,与引用的对象无关。
- str,bytes,array.array等单一类型序列是扁平的,保存的不是引用,而是在连续的内存中保存数据本身
- 元组中不可变的是元组的标识
8.3、默认做浅复制
>>> l1 = [3,4,5]
>>> l2 - list(l1)
>>> l2 == l1
>>> True
>>> l2 is l1
>>> False
- 浅复制:复制了最外层容器,副本中的元素是源容器中元素的引用。如果所有的元素都是不可变的,那么没有问题还能节省内存。但是,如果有可变的元素,可能会导致意想不到的问题。
- 深复制:副本不共享内部对象的引用
- copy模块
- deepcopy 为任意对象做深复制
- copy 为任意对象做浅复制
- 我们可以实现特殊方法
__copy__()
和__deepcopy__
控制copy和deepcopy的行为
8.4、函数的参数作为引用时
- 共享传参指函数的各个形式参数获得实参中各个引用的副本。也就是说,函数内部的形参是实参的别名。
8.4.1、不要使用可变类型作为参数的默认值
- 没有指定初始乘客的HauntedBus实例会共享同一个乘客列表
- 默认值在定义函数时计算(通常在加载模块时),因此默认值变成了函数对象的属性。
- 除非这个方法确实像修改通过参数传入的对象,否则在类中直接把参数赋值给实例变量之前一定要三四,因为这样会为参数对象创建别名。如果不确定,那就创建副本。
8.5、del和垃圾回收
- del语句删除名称,而不是对象。del命令可能会导致对象被当做垃圾回收,但是仅当删除的变量保存的是对象的最后一个引用,或者无法得到的对象时。
- 在CPython中,垃圾回收使用的主要算法是引用计数。每个对象会桶机油多少引用指向自己,当引用计数归零时,对象立即被销毁。
- weakref
- del不删除对象,只是删除对象的引用
8.6、弱引用
因为有引用,对象才会在内存中存在。当对象的引用数量归零后,垃圾回收程序会把对象销毁。
- 弱引用不会增加对象的引用数量
- weakref模块
- 建议使用高级接口,如下
- WeakKeyDictionary
- WeakValueDictionary
- WeakSet
- finalize
8.6.2、弱引用的局限
- 基本的list和dict示例不能作为所指对象,但是他们的子类可以轻松的解决这个问题。
- set实例可以作为所指对象。
- int和tuple示例不能作为弱引用的目标,甚至是子类也不行。
8.7、Python对不可变类型施加的把戏
- 使用另一个元组构建元组,得到的起始是同一个元组
- 对于元组t来说,t[:]不创建副本,而是返回对象的引用
- 字符串字面量可能会创建共享的对象
- 共享字符串字面量是一种优化措施,称为驻留(interning)。Cpython还会再小的整数上使用这个优化措施,放置创建热门数字。
- 比较字符串或整数是否相等,应该使用==
9、符合Python风格的对象
- 支持用于生成对象其他表示形式的内置函数,如(repr(), bytes()等)
- 使用一个类方法实现备选构造方法
- 扩展内置的format函数和str.format()方法使用的格式微语言
- 实现制度属性
- 把对象变为可散列的,一边在集合中以及dict的键使用
- 利用
__slots__
节省内存
9.1、对象表示形式
- repr():便于开发者理解的方式返回对象的字符串表示形式
- str():以便于用户理解的方式返回对象的字符串表示形式
- 为了给对象提供其他的表示形式,还会用到另外两个特殊方法,
-
__bytes__
bytes()函数调用他获取对象的字节序列表示形式 -
__format__
:内置的format()函数和str.format()方法调用
-
-
__iter__
方法,把示例编程可迭代的对象
9.3、备选构造方法
9.4、classmethod与staticmethod
- classmethod:最常见的用途是定义备选构造方法
- staticmethod:静态方法就是普通的函数,只是碰巧在类的定义体中,而不是在模块层定义。
- classmethod装饰器非常有用,我从未见过不得不用staticmethod的情况。如果想定义不需要与类交互的函数,那么在模块中定义就好了。
9.5、格式化显示
9.6、可散列的
- 使用两个前导下划线,把属性标记为私有的
- 创建可散列的类型,不一定要实现特性,不一定要保护实例属性。只需要正确实现
__hash__
和__eq__
方法即可。但是市里的散列值绝对不应该变化,因此我们只是借机提到了只读属性
9.8、使用slots属性节省空间
-
__slots__
让解释器在元祖中存储示例属性,而不用字典 - 继承自超类的
__slots__
属性没有效果,Python只会使用各个类中定义的__slots__
属性 - 一般用元组
-
__slots__
属性的目的是告诉解释器,这个类中的所有实例属性都在这了 - 每个子类要定义
__slots__
属性,因为解释器会忽略继承的__slots__
属性
10、序列的修改,散列和切片
10.3、协议和鸭子类型
- Python的序列协议只要
__len__
和__getitem__
两个方法。 - 协议是非正式的,没有强制力,如果知道类的具体使用场景,如:为了支持迭代,只需事先
__getitem__
方法,没必要提供__len__
方法
10.4.1、切片原理
-
__getitem__()
返回的是传给他的值 - slice类
- 有start, stop, step数据属性
- S.indices(len) -> (start, stop, stride)。给定长度len的序列,计算S表示的扩展切片的起始,结尾和索引。
- indices方法优雅的处理确实索引和负数索引,以及长度超过目标序列的切片。
- 大量使用isinstance可能表名面向对象设计的不好,不过在
__getitem__
方法中使用它处理切片是合理的。 - 使用抽象基类做测试能让API更灵活且更容易更新。
10.5、动态存取属性
- 对my_obj.x表达式,Python会检查实例中有没有名为x的属性;如果没有,到类
my_obj.__class__
中查找,如果还没有,顺着继承树继续查找。如果依旧找不到,调用my_obj所属类中定义的__getattr__
方法,传入self和属性名称的字符循环形式。 -
__getattr__
当对象没有指定名称的属性时,python会调用那个方法,这是一种后备机制。可是像v.x=10赋值后,v对象有x属性了,因此使用v.x获取x属性的值时不会调用__getattr__
方法了,解释器直接返回绑定到v.x上的值。
11、从协议到抽象基类
11.1、python文化中的接口和协议
- 白鹅类型:只要cls是抽象基类,即cls的元类是abc.ABCMeta,就可以使用isinstance(obj, cls)
- 抽象基类的本质就是特殊的方法。如,只要实现了特殊方法
__len__
即可被识别为abc.Sized的子类 - 如果实现的类体现了抽象基类的概念,要么继承相应的抽象基类,要么把类注册到相应的抽象积累中。
11.5、定义抽象基类的子类
11.6、标准库中的抽象基类
- Iterable 通过
__iter__
方法支持迭代 - Container通过
__contains__
方法支持in运算 - Sized通过
__len__
方法支持
11.6.2 抽象基类的数字塔
numbers包定义的数字塔,Number是位于最顶端的超类,随后是Complex子类,依次往下
- Number
- Complex
- Real
- Rational
- Integral
如果想检查一个数是不是整数,可以使用instance(x, numbers.Integral),这样代码就能接受int,bool或者外部库使用numbers抽象类注册的其他类型。 - 自定义抽象基类
- 自定义抽象基类要继承 abc.ABC
- 抽象方法使用@abstractmethod装饰器标记
- 抽象基类可以包含具体方法
- 具体方法只能依赖抽象类定义的接口(只能使用抽象基类中其他方法,抽象方法或特性)
- 抽象方法可以有实现代码。即使实现了,子类也必须覆盖抽象方法,但是在子类中可以使用super()函数调用抽象方法,而不是从头开始实现。
- 抽象基类句法详解
- 声明抽象基类最简单的方式是继承 abc.ABC或其他抽象基类
- 可以在class语句中使用metaclass=关键字,把值设为abc.ABCMeta
- 在函数上堆叠装饰器的顺序很重要,abstractmethod()应该放在最里面。
11.9、python使用register的方式
在 collections.abc模块的源码中,使用
Sequence.register(tuple)
Sequence.register(str)
Sequence.register(range)
Sequence.register(memoryview)
注册为Sequence虚拟子类的
12、继承的优缺点
- 内置类型可以子类化,但是有个重要的注意事项:内置类型基本不会调用用户定义的类覆盖的特殊方法。
- 因为内置类型的方法通常会忽略用户覆盖的方法,不要子类化内置类型,用户自己定义的类应该继承collections模块中的类,如UserDict, UserList和UserString。这些类做了特殊设计,易于扩展。
- 内置类型不会调用用户定义的类覆盖的方法,只会发生在C语言实现的内置类型内部的方法委托上,而且只影响直接继承内置类型的用户自定义类。如果子类使用Python编写的类,如UserDict,MutableMapping,就不会受此影响。
12.2、多重继承和方法解析顺序
- 类都有
__mro__
属性,值是一个元组,按照方法解析顺序列出各个超类。直到object - python会按照特定的顺序遍历继承图,解析各个超类,从当前类一直向上,直到object对象。
- 若想把方法调用委托给超类,推荐的方法是使用内置的super()函数,也可以直接调用某个超类的方法,这样做有时更方便。
def ping(self):
A.ping(sef)
print('post-ping:', self)
- 方法解析顺序不仅考虑继承图,还考虑子类声明中列出超类的顺序。
13、正确重载运算符
运算符重载的作用是让用户定义的对象使用中缀运算符,如+和|或一元运算符如-和~。
13.1、运算符重载基础
- 不能重载内置类型的运算符
- 不能新建运算符,只能重载现有的
- 某些运算符不能重载,is,and,not和or(不过位运算符& | 和~可以)
13.2、一元运算符
-
- (__neg__)
一元取负运算符, x == -x -
+ (__pos__)
取正运算符, x == +x -
~ (__invert__)
对整数按位取反,~x == -(x+1) abs() __abs__
14、可迭代的对象,迭代器和生成器
-
使用iter()内置函数处理可迭代的对象
-
使用python实现经典的迭代器模式
-
生成器函数的工作原理
-
使用生成器函数或生成器表达式代替经典的迭代器
-
使用标准库中通用的生成器函数
-
使用yield-from语句合并生成器
-
序列可迭代的原因:iter函数
- 检查对象是否实现了
__iter__
方法,如果实现了就调用他,范湖一个迭代器。 - 如果没有实现
__iter__
方法,但是实现了__getitem__
方法,则python会创建一个迭代器,尝试按索引顺序获取元素。 - 如果尝试失败,python跑出TypeError异常,通常提示C is not iterable
- 检查对象是否实现了
-
检查对象是否可迭代,使用iter(x)方法。
14.2、可迭代的对象与迭代器的对比
-
可迭代的对象
- 使用iter内置函数可以获取迭代器的对象。
-
关系
- python从可迭代的对象中获取迭代器
-
迭代器
- 实现了
__next__
方法 ,返回序列中的下一个元素。 - 实现
__iter__
方法
- 实现了
-
迭代器设计模式
- 访问一个聚合对象的内容而无需暴露他的内部表示
- 支持对聚合对象的各种遍历
- 对遍历不同聚合结构提供一个统一的接口
-
标准库中的生成器函数
- itertools.compress(it, selector_it)并行迭代2个对象,如果selector_it中的元素是真值,产出it中对应的元素。
- itertools.dropwhile(predicate, it)处理it,跳过precidate的计算结果为真值的元素,产出剩下的各个元素
- itertools.filterfalse() 与filter逻辑相反
- itertools.islice(it, stop)产出切片,实现的是惰性操作。
- itertools.takewhile(predicate, it)返回真值产出对应的元素
-
用于映射的生成器函数
- itertools.accumulate(it ,f)产出累计的总和
- enumerate
- map
- itertools.startmap(f, it)。把it中各个元素传给test,产出结果;输入可迭代对象应该产出可迭代的元素iit,然后以func(*iit)形式调用func
- 可迭代对象可通过*拆包
-
合并多个可迭代对象的生成器函数
- itertools.chain(it1, it2)产出it1中的所有元素,然后产出it2中的所有元素,以此类推,无缝连接在一起。
- itertools.chain.from_iterable(it)产出it生成的各个可迭对象中的元素,一个接一个,无缝连接在一起。it应该产出可迭代的元素。it应该产出可迭代的元素,例如可迭代的对象列表。
- zip(it1,...,itN)并行从输入的各个可迭代对象中获取元素,产出由N个元素组成的元组,只要有一个可迭代的对象到头了,就停止。
- zip_longest(it1, ..., itN, fillvalue=None)等到最长的可迭代对象到头后才停止,空缺的值使用fillvalue填充。
-
用于重新排列元素的生成器函数
- itertools.groupby(it, key=None)产出由两个元素组成的元素,形式为(key, group)其中key是分组标准,group是生成器,用于产出分组里的元素。
it = itertools.groupby(['a', 'ab', 'bc', 'abc'], key=len)
- reversed(seq)从后向前,倒序产出seq中的元素,seq必须是序列,或者实现了
__reversed__
特殊方法的对象。 - itertools.tee(it, n=2)产出一个由n个生成器组成的元组,每个生成器用于单独产出驶入可迭代对象中的元素。
- itertools.tee函数产出多个生成器,默认为2个。
14.10、python3.3新出现语法yield from
- 如果生成器函数需要产出另一个生成器生成的值,传统的解决方法是使用嵌套的for循环。
- yield from i完全代替了内层的for循环。yield from还会创建通道,把内层生成器直接与外层生成器的客户端联系起来。
14.11、可迭代的规约函数
- all(it) it中所有元素都为真值时返回True,否则返回False,all([])返回True
- any(it)只要it中有元素为真就返回True,否则返回False。any([])返回False
- max(it, [key=,][default=])
- min(it, [key=,][default=])
- functools.reduce(func, it, [])把前两个元素传给func,然后把计算结果和第三个元素传给func,以此类推,返回最后的结果,如果提供了initial,把他当做第一个元素传入
- sum(it, start=0)
14.12、深入分析iter函数
iter函数有一个鲜为人知的用法,传入两个参数,使用常规的函数或任何可调用的对象创建迭代器。第一个参数必须是可调用的对象,用于不断调用(没有参数),产出各个值,第二个值是哨符,是标记值,当可调用的对象返回这个值时,触发迭代器跑出StopIteration异常,不产出哨符。
14.14、把生成器当成协程
- python2.5为生成器方法添加了额外的方法和功能,最值得关注的是.send()方法。
- .
__next__()
只允许客户从生成器获取数据 - .
send()
允许客户代码和生成器之间双向交换数据。.send()方法允许使用生成器的客户把数据发给自己,即不管传给.send()方法什么参数,参数都会成为生成器函数定义体中对应的yield表达式的值。- 生成器用于生成供迭代的数据
- 协程是数据的消费者
- 为了避免脑袋炸掉,不能把这两个概念混为一谈
- 协程与迭代无关
- 虽然在协程中会使用yield产出值,但这与迭代无关。
15、上下文管理器和else块
- with语句和上下文管理器
with语句会设置一个临时的上下文,交给上下文管理器对象控制,并且负责清理上下文,更安全易于使用,除了自动关闭文件之外,with块还有跟多用途。
- for, while 和 try 语句的 else 子句
- for仅当for循环运行完毕时(没有被break语句终止),才运行else块
for item in my_list: if item.flavor == 'banana': break else: raise ValueError('No banana flavor found!')
- while 仅当while循环因为条件为假值而退出时(while循环没有被break语句终止)
- try 仅当try块中没有异常抛出时才运行else块。
# try块中应该只抛出预期异常的语句 try: dangerous_call() except OSError: log('OSError...') else: after_call()
- 所有情况下,如果异常或者return,break,continue语句导致控制权跳到了复合语句的主块之外,else子句也会被跳过。
- EAFP(easier to ask for forgiveness than permission)取得原谅比获得许可容易。常见的Python编程风格,先假定存在有效的键或属性,假定不成立那么捕获异常
- LBYL (look before you leap)三思而后行。在调用函数或查找属性或键之前显示测试前提条件,这种风格的特点是代码中有很多的if语句
- 如果选择使用EAFP风格,要更深入的了解else子句,并且在try/except语句中合理使用。
15.2、上下文管理器和with块
-
with语句的目的是简化try/finally模式,这种模式用于保证一段代码运行完毕后执行某项操作,即便那段代码由于异常,return语句或sys.exit()调用而终止,也会执行指定的操作。
-
上下问管理器协议包含
__enter__
和__exit__
两个方法。with语句开始运行时,会在上下文管理器对象上调用__enter__
方法。with语句运行结束后,会在上下文管理器对象上调用__exit__
方法,以此扮演finally子句的角色。 -
执行with后面的表达式得到的结果是上下文管理器对象,不过把值绑定到目标变量as是在上下文管理器对象上调用
__enter__
方法的结果。 -
不管控制流程以哪种方式退出with块,都会在上下文管理器对象上调用
__exit__
方法,而不是在__enter__
方法返回的对象上调用。 -
解释器调用
__enter__
方法时除了隐式的self之外,不会传入任何参数。 -
传给
__exit__
方法的三个参数列举如下:- exc_type异常类例如ZeroDivisionError
- exc_value异常实例
- traceback对象
15.3、contextlib模块中的使用工具
- closing如果对象提供了close()方法,但没有实现
__enter__/__exit__
协议,那么可以使用这个函数构建上下文管理器。 - suppress构建临时忽略指定异常的上下文管理器
- @contextmanager装饰器把简单的生成器函数编程上下文管理器,就不用创建类去实现管理器协议了。
- ContextDecorater
- 基类,用于定义基于类的上下文管理器。这种上下文管理器也能用于装饰函数,在受管理的上下文中运行整个函数。
- ExitStack
- 上下文管理器能进入多个上下文管理器。with块结束时,ExistStack按照后进先出的顺序调用栈中各个上下文管理器的
__exit__
方法。如果事先不知道with块要进入多少个上下文管理器,可以使用这个类。
- 上下文管理器能进入多个上下文管理器。with块结束时,ExistStack按照后进先出的顺序调用栈中各个上下文管理器的
15.4、使用@contextmanager
- 使用@contextmanager装饰的生成器中,yield语句的作用是把函数的定义体分成两个部分:yield语句前面的所有代码在with块开始时(解释器调用
__enter__
方法时)执行,yield语句后面的代码在with块结束时执行。
16、协程
- 协程和生成器类似,都是定义体中包含yield关键字的函数
- 协程中yield通常出现在表达式的右边,可以产出值,也可以不产出,如果yield关键字后面没有表达式,生成器产出是None
- 协程可能会从调用方接收数据,通过.send()方法。
16.1、生成器进化成协程
-
协程是指一个过程,这个过程与调用方协作,产出由调用方提供的值。
-
.send(...)
-
.throw(...)调用方抛出异常
-
.close()终止生成器
-
引入了yield from句法,可以吧复杂的生成器重构成小型的嵌套生成码,省去之前把生成器的工作委托给子生成器所需要的大量样板代码。
-
协程可以身处4个状态中的一个。使用inspect.getgeneratorstate(...)函数确定
- GEN_CREATED 等待开始执行
- GEN_RUNNING 解释器正在执行
- GEN_SUSPENDED 在yield表达式处暂停
- GEN_CLOSED 执行结束
-
因为send方法的参数会成为暂停的yield表达式的值,所以仅当协程处于暂停状态时才能调用send方法。始终调用next(...)激活协程,或者调用.send(None)效果一样。
16.3、使用协程计算移动平均值
def ave():
total, count, average = 0.0, 0, None
while True:
term = yield average
total += term
count += 1
average = total/count
- 启动携程前必须激活,可是这一步容易忘记。为了避免忘记,可以在协程上使用一个特殊的装饰器。
16.4、预激协程的装饰器
16.5、终止协程和异常处理
客户代码可以在生成器对象上调用两个方法,显示的把异常发给协程
- throw
- generator.throw(exc_type[, exc_value[, traceback]]) 致使生成器在暂停的yield表达式处抛出指定的异常。如果生成器处理了跑出的异常,代码会向前执行到下一个yield表达式,产出的值会成为调用generator.throw方法得到的返回值。如果生成器没有处理异常,异常会向上冒泡,传到调用方的上下文中。
- 如果无法处理传入的异常,协程会终止
- close
- generator.close() 致使生成器在暂停的yield表达式处抛出GeneratorExit异常。如果生成器没有处理这个异常,或者抛出了StopIteration异常,调用方不会报错
python 3.3引入yield from结构的主要原因之一与把异常传入嵌套的协程有关。另一个原因是让协程更方便的返回值。
16.6、让协程返回值
捕获StopIteration异常,获取averager返回的值。yield from 结构会在内部自动捕获StopIteration异常。循环机制使用用户易于理解的方式处理异常。对于yield from结构来说,解释器不仅会捕获StopIteration异常,还会把value属性的值变成yield from表达式的值。
16.7、yield from
yield from x 表达式对x对象所做的第一件事是,调用iter(x),从中获取迭代器。因此x可以是任何可迭代的对象。
yield from结构的本职作用无法通过简单的可迭代对象说明,而要发散思维,使用嵌套的生成器
yield from的主要功能是打开双向通道,把最外层的调用方和最内层的子生成器连接起来,二者可以直接发送和产出值,还可以直接传入异常。
-
委派生成器
- 包含yield from <iterable> 表达式的生成器函数
-
子生成器
- 从yield from 表达式中<iterable>部分获取的生成器
-
调用方
-
threading
thread_ = threading.Thread()
thread_.start() # 开始
thread_.join() # 阻塞到结束
17、使用期物处理并发
- 期物:future指一种对象,表示异步执行的操作。是concurrent.futures模块和 asyncio包的基础
17.1、示例
- 使用concurrent.futures模块示例
from concurrent import futures
with futures.ThreadPoolExecutor(10) as executor:
res = executor.map(fun1, list_args)
return len(list(res)) # 从此处会阻塞
17.1.3、期物在哪
- concurrent.futures.Future和asyncio.Future两个类的示例都表示已经完成或者尚未完成的延迟计算
- 通常情况自己不应该创建期物,只能由并发框架实例化。因此只有排定把某件事交给concurrent.futures.Executor子类处理时,才会创建Future实例。
- executor.submit()方法参数是一个可调用对象,调用这个方法后会为传入的可调用对象排期,并返回一个期物。
- .done()方法,两种期物都不阻塞,返回布尔值,指明期物链接的可调用对象是否已经执行。
- .add_done_callback()方法,期物运行结束后会调用指定的可调用对象。
- .result()在期物运行结束后调用的话,在两个Future类作用相似。如果没有运行结束,则行为相差很大。concurrent的会阻塞线程,接收可选的timeout参数。asyncio不支持设定超时时间,获取期物最好的结果是使用yield from结构。
- concurrent.futures.as_completed函数,参数是一个期物列表,返回值是一个迭代器。会阻塞。
17.2、阻塞型I/O和GIL
- 标准库中所有执行阻塞型I/O操作的函数,在等待操作系统返回结果时,都会释放GIL
17.3、使用concurrent.futures模块启动进程
- ProcessPoolExecutor和ThreadPoolExecutor类都实现了Executor接口。唯一值得注意的区别是ThreadPoolExecutor方法需要max_workers参数指定线程数量。而ProcessPoolExecutor参数是可选的,默认是os.cpu_count()函数返回的数量。
- executor.map不会阻塞
- enumerate()会隐式调用next(results),会阻塞。
- .submit方法和.as_completed函数结合使用
18、使用asyncio包处理并发
适合asyncio的协程在定义体中必须使用yield from而不能使用yield,此外 asyncio 协程要由调用方驱动;或者把协程传给asyncio包中的某个函数如asyncio.async(...)
@asyncio.coroutine装饰器应该应用在协程上
- asyncio.Task 和 threading.Thread
- task对象用于驱动协程,Thread对象用于调用可调用的对象
- task对象不由自己动手实例化,而是通过把协程传给asyncio.async()函数或loop.creat_task()获取,获取的Task对象已经排定了运行时间,Thread示例则必须调用 start方法,明确告知让他运行。
- 函数由线程调用,协程一般由yield from驱动
18.1.1、asyncio.Future故意不阻塞
- asyncio.Future类和concurrent.futures.Future类的接口基本一致,不过实现方式不同,不可以互换。
- asyncio中BaseEventLoop.create_task()方法接受一个协程,排定运行时间,然后返回asyncio.Task实例,也是asyncio.Future类。和executor.submit()方法是一个道理。
- .result()方法没有参数,如果调用的时候期物还没运行完毕,那么.result()方法不会阻塞去等待结果,而是抛出asyncio.InvalidStateError异常。
- 使用yield from处理期物,等待期物处理完毕这一步无需关心,而且不会阻塞时间循环。在asyncio包中,yield from 的作用是把控制权还给事件循环。
- 无需调用.add_done_callback()因为可以直接把想在期物运行结束执行的操锁放在协程 yield from my_future后面
- 无需调用.result()因为yield from从期物中产出的就是这个值
18.1.2、从期物,任务和协程中产生
- 如果 foo是协程函数,yield from foo返回Future或Task实例普通函数。res = yield from foo()
- 必须排定协程的运行时间,使用asyncio.Task对象包装
- asyncio.async(coro_or_future, *, loop=None)
统一了协程和期物:第一个参数是二者中的任何一个,如果是Future或Task对象,就原封不动的返回。如果是协程,async会调用loop.create_task()创建task对象。loop是可选的,入股没有传入,async函数会调用asyncio.get_event_loop()函数获取循环对象。
- BaseEventLoop.create_task(coro)
这个方法排定协程的执行时间,返回一个asyncio.Task对象
asyncio包中有多个函数会自动把参数指定的协程包装在asyncio.Task对象中,例如BaseEventLoop.run_until_complete()
-
asyncio.wait()协程的参数是一个由期物或协程构成的可迭代对象;wait会分别把各个协程包装进一个Task对象,最终的结果是,wait处理的所有对象通过某种方式变成Future类的实例。wait是协程函数,因此返回的是一个协程或生成器对象。
-
为了驱动协程,我们把协程传给loop.run_until_complete()方法
-
loop.run_until_complete方法的参数是一个期物或协程。如果是协程,则与wait函数一样,把协程包装进一个Task对象。协程,期物和任务都能由yield from驱动
-
示例:
@asyncio.coroutine
def get_flag(cc):
url = ''
resp = yield from aiohttp.request('GET', url)
image = yield from resp.read()
return image
def get_flag(cc):
url = ''
resp = aiohttp.request('GET', url)
image = resp.read()
return image
- yield from foo句法能防止阻塞,因为当前协程暂停后,控制权回到事件循环手中,再去驱动其他协程。foo期物或协程运行完毕后,把结果返回给暂停的协程,将其恢复
- summary
- 使用yield from链接的多个协程最终必须由不是协程的调用方驱动,调用方显示或隐式(例如for循环中)在最外层委派生成器上调用next(...)函数或.send()方法
- 链条中最内层的子生成器必须是简单的生成器(只使用yield)或可迭代的对象。
- 在 asyncio包的API中使用yield from两点都要成立。不过要注意下述细节
- 协程链条始终通过把最外层委派生成器传给 asyncio 包API中的某个函数,如loop.run_util_complete()驱动。也就是说,使用asyncio包时,我们编写的代码不通过调用next()函数或.send()方法驱动协程,这一点由asyncio包实现的事件循环去做。
- 我们编写的协程链条最终通过 yield from 把职责委托给asyncio包中的某个协程函数或协程方法,如 yield from asyncio.sleep()或者其他库中实现高层协议的协程(如 resp=yield from aiohttp.request('GET', url))
最内层的子生成器是库中真正执行I/O操作的函数,而不是我们自己编写的函数
使用asyncio包,我们编写的异步代码中包含由asyncio本身驱动的协程(即委派生成器),而生成器最终把职责委托给asyncio包或第三方库中的协程。这种处理方式相当于假期了管道,让asyncio时间循环驱动执行底层异步I/O操作的库函数
19、元编程
19.1、动态属性
- keyword.iskeyworld()判断对象是不是关键词
19.1.3、使用__new__
方法灵活的方式创建对象
-
__new__
用于构建实例的方法,这是个类方法,使用特殊方法处理,不必使用@classmethod装饰器,必须返回一个实例。 - 返回的实例作为第一个参数传给
__init__
方法。 -
__init__
方法要传入实例,而且禁止返回任何值,所以__init__
方法其实是初始化方法。真正的构造方法是__new__
-
__new__
方法也可以返回其他类的实例,此时不会调用__init__
方法。 -
__dict__
- 对象的
__dict__
属性存储着对象的属性,前提是类中没有声明__slots__
属性。
- 对象的
19.2、使用特性验证属性
-
使用@property装饰器实现只读特性
-
实现name的读写属性
class A: def __init__(self, value): self.name = value # 此处已经使用特性的设置方法了 @property def name(self): return self.__name @name.setter def name(self, value): self.__name = value
-
python交互式测试方式:python -i f1.py
19.3、特性全解析
property构造方法的完整签名如下
property(fget=None, fset=None, fdel=None, doc=None)
- 所有的参数是可选的,如果没有把函数传给某个参数,得到的特性对象就不允许执行相应的操作
19.3.1、特性会覆盖实例属性
- 实例属性遮盖类的数据属性
- 实例属性不会遮盖类特性(使用@property修饰)
- 新添的类特性遮盖现有的实例属性
- summary
obj.attr这样的表达式不会从obj开始寻找attr,而是从obj.__class__开始,仅当类中没有名为attr的特性时,python才会在obj实例中寻找。
19.3、特性的文档
class A:
@property
def bar(self):
'''the bar attribute'''
return self.__dict__['bar']
@bar.setter
def bar(self, value):
self.__dict__['bar'] = value
- 使用helo(A.bar)查看文档
19.4、定义一个特性工厂函数
class LineItem:
weight = quantity('weight')
price = quantity('price')
def __init__(self, weight, price):
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
def quantity(name):
def name(instance):
return instanch.__dict__[name]
def name_set(instance, value):
if value > 0:
instance.__dict__[name] = value
else:
raise ValueError('value must be > 0')
- self.weight和self.price的每个引用都由特性函数处理,只有直接存储
__dict__
属性才能跳过特性处理逻辑 - 真实的系统中,分散在多个类中的多个字段可能要做同样的验证,最好把quantity工厂函数放到实用工具模块中,一面重复使用。
19.5、处理属性删除操作
- 对象的属性可以使用del语句删除
- del my_object.an_attribute
- @member.deleter
- 如果不使用特性,还可以实现低层特殊的
__delattr__
方法处理删除属性。
19.6、处理属性的重要属性和函数
19.6.1、
-
__class__
- 对象所属类的引用。python的某些特殊方法,例如
__getattr__
只在对象的类中寻找,而不是在实例中寻找。
- 对象所属类的引用。python的某些特殊方法,例如
-
__dict__
- 一个映射,存储对象或类的可写属性。有
__dict__
属性的对象,任何时候都能随意设置新属性。如果类有__slots__
属性,他的实例可能没有__dict__
属性。
- 一个映射,存储对象或类的可写属性。有
-
__slots__
- 类可以定义这个属性,限制实例能有哪些属性。
__slots__
属性的值是一个字符串组成的元组,指明允许有的属性。如果__slots__
中没有'dict',那么该类的实例没有__dict__
属性,实例只允许有指定名称的属性。子类不受父类的限制。
- 类可以定义这个属性,限制实例能有哪些属性。
19.6.2、处理属性的内置函数
- dir([object])
- dir函数的目的是交互式使用,因此只列出一组重要的属性名。dir函数能审查有没有
__dict__
属性的对象。dir函数不会列出__dict__
属性本身,但会列出其中的键。dir不会列出如__mro__
,__bases__
,__name__
。如果没有指定可选的object函数,dir函数会列出当前作用域的名称。
- dir函数的目的是交互式使用,因此只列出一组重要的属性名。dir函数能审查有没有
-
__mro__(method resolution order)
多继承属性查找 - getattr(object, name)
- 从object对象获取name字符串对应的属性。如果没有指定属性,抛出AttributeError
- hasattr(object, name)
- 如果object对象中存在指定的属性,或者能以某种方式通过object 对象获取指定的属性。返回True
- setattr(object, name, value)
- 把object对象的指定属性设置为value
- vars([object])
- 返回对象的
__dict__
属性
- 返回对象的
- dis
import dis
dis.dis(func)
19.6.3、处理属性的特殊方法
- 使用点好或者内置的getattr,hasattr和setattr函数存取属性都会触发下述列表中相应的特殊方法。但是通过实例
__dict__
属性读写属性不会触发这些特殊的方法,通常会使用这种方式跳过特殊方法。 -
__delattr__
- 只要使用del语句删除属性,就会调用这个方法。
-
__dir__
- 把对象传给dir函数时,列出属性。
-
__getattr__
- 仅当获取指定的属性失败,搜索过obj, Class, 超类之后调用。仅当在obj,Class和超类中找不到指定的属性时可能会触发Class.getattr方法
-
__getattribute__
- 获取指定的属性总会调用,不过寻找的属性时特殊属性或特殊方法除外。点号和getattr,hasattr会触发。调用
__getattribute__
方法且抛出AttributeError异常时,会调用__getattr__
方法。方法的实现要使用super().__getattribute__(obj, name)
- 获取指定的属性总会调用,不过寻找的属性时特殊属性或特殊方法除外。点号和getattr,hasattr会触发。调用
-
__setattr__
- 设置指定的属性时,总会调用。点号和setattr内置方法会触发这个方法
20、属性描述符
20.1、描述符示例
- 实现了
__get__
,__set__
, 或__delete__
方法的类时描述符。用法是创建一个实例,作为另一个类的类属性。 - 描述符类
- 实现描述符协议的类
- 托管类
- 把描述符实例声明为类属性的类
20.2、覆盖型与非覆盖型描述符对比
python 存取属性的方式特别不对等。实例读取属性时,通常返回的是实例中定义的属性,如果实例中没有指定的属性,那么会获取类属性。而为实例中的属性赋值时,通常会在实例中创建属性,不会影响类。
容器,迭代器,生成器
- 容器:实现
__getitem__()
方法即可 - 迭代器:实现
__iter__()
和__next__()
方法 - 生成器:包含
__iter__()
和__next__()
方法 -
__iter__()
返回的一定是迭代器对象
容器一定是可迭代的;
生成器一定是迭代器;
实现__iter__()和__next__()方法的是迭代器;
如果函数中出现了yield关键字,那么该函数就不再是普通函数,而是生成器函数。