[TOC]
1.鸭子模型
在程序设计中,鸭子类型(duck typing)是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由”当前方法和属性的集合”决定。“鸭子测试”可以这样表述:
一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟可以被称为鸭子“
在鸭子类型中,关注点在于对象的行为,能作什么;而不是关注对象所属的类型。例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为”鸭子”的对象,并调用它的”走”和”叫”方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的”走”和”叫”方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的”走”和”叫”方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。
鸭子类型通常得益于”不”测试方法和函数中参数的类型,而是依赖文档、清晰的代码和测试来确保正确使用。
#使用鸭子类型处理单个字符串或由字符串组成的可迭代对象
try:
field_names = field_names.replace(',',' ').split()
except AttributeError:
pass
field_names = tuple(field_names)
2.特殊方法
python解释器碰到特殊的句法时,会使用特殊方法去激活一些基本的对象操作,这些特殊方法的名字以两个下划线开头,以两个下划线结尾(例如__getitem__)。比如obj[key]背后就是__getitem__()方法,为了能求得my_collection[key]的值,解释器实际上会调用mycollection.__getitem__(key)。这些特殊方法名能让你自己的对象实现和支持以下的语言框架,并与之交互:
- 迭代
- 集合类
- 属性访问
- 运算符重载
- 函数和方法的调用
- 字符串表示形式和格式化(基础方法)
- 管理上下文(用with块)
- 序列化
- 对象的创建和销毁
2.1 python命名规则
- “单下划线”开始的成员变量叫做保护变量,意思是只有类对象和子类对象自己能访问到这些变量
- ”双下划线“开始是私有成员,意思是只有类对象自己能访问,连子对象也不能访问到这个数据
- ”双下划线“开始,”双下划线“结束的函数代表python里特殊方法专用的标识。
2.2 类的基础方法
目的 | 代码 | python实际调用 |
---|---|---|
初始化一个实例 | x = Myclass() | x.__init__() |
字符串的“官方”表现形式 | repr() | x.__repr__() |
字符串的“非正式”值 | str() | x.__str__() |
字节数组的“非正式“值 | bytes(x) | x.__bytes__() |
格式化字符串的值 | format(x,format_spec) | x.__format__(format_spec) |
2.3行为方式与迭代器类似的类
目的 | 代码 | python实际调用 |
---|---|---|
遍历某个序列 | iter(seq) | seq.__iter__() |
从迭代器中获取下一个值 | next(seq) | seq.__next__() |
按逆序创建一个迭代器 | reversed(seq) | seq.__reversed__() |
2.4 集合类
2.4.1 行为方式与序列类似的类
目的 | 代码 | python实际调用 |
---|---|---|
序列的长度 | len(seq) | seq.__len__() |
了解某序列是否包含特定的方法 | x in seq | seq.__contain__(x) |
2.4.2 行为方式与字典类似的类
目的 | 代码 | 实际调用 |
---|---|---|
通过键来获取值 | x[key] | x.__getitem__(key) |
通过键来设置值 | x[key] = value | x.__setitem__(key,value) |
删除一个键值对 | del x[key] | x.__delitem__(key) |
为缺失提供默认值 | x[nonexistent_key] | x.__missing__(nenexistent_key) |
2.4 属性访问
目的 | 代码 | python实际调用 |
---|---|---|
获取一个计算属性 | x.my_property | x.__getattribute__(‘my_property’) |
获取一个计算属性(后备) | x.my_property | x.__getattr__(‘my_property’) |
设置某属性 | x.my_property = value | x.__setattr__(‘my_property,value’) |
删除某属性 | del x.my_property | x.__delattr__(‘my_property’) |
列出所有属性和方法 | dir(x) | x.__dir__() |
2.5 运算符重载
2.5.1比较运算符
目的 | 代码 | python实际调用 |
---|---|---|
相等 | x == y | x.__eq__(y) |
不相等 | x != y | x.__ne__(y) |
小于 | x < y | x.__lt__(y) |
小于或等于 | x <= y | x.__le__(y) |
大于 | x > y | x.__gt__(y) |
大于或等于 | x >= y | x.__ge__(y) |
布尔上下文环境中的真值 | if x: | x.__bool__() |
2.5.2 计算运算符
目的 | 代码 | python实际调用 |
---|---|---|
加法或拼接 | + | x.__add__(y) |
减法 | - | x.__sub__(y) |
乘法或重复复制 | × | x.__mul__(y) |
除法 | / | x.__truediv__(y) |
整除 | // | x.__floordiv__(y) |
取模 | % | x.__mod__(y) |
返回由整除的商和模数组成的元祖 | divmod() | x.__divmod__(y) |
取幂 | **,pow() | x.__pow__(y) |
矩阵乘法 | @ | x.__matmul__(y) |
2.6 行为方式与函数类似的类
目的 | 代码 | python实际调用 |
---|---|---|
像调用函数一样”调用“一个实例 | my_instance() | my_instance.__call__() |
2.7 可在with语块中使用的类
目的 | 代码 | python实际调用 |
---|---|---|
在进入with语块时进行一些特别操作 | with x: | x.__enter__() |
在退出with语块时进行一些特别操作 | with x: | x.__exit_|_() |
- __exit__()方法将总是被调用,哪怕是在with语块中引发了例外。实际上,如果引发了例外,该例外信息将会被传递给__exit__()方法。
2.8 可序列化的类
目的 | 代码 | python实际调用 |
---|---|---|
自定义对象的复制 | copy.copy(x) | x.__copy__() |
自定义对象的深度复制 | copy.deepcopy(x) | x.__deepcopy__() |
在picking之前获取对象的状态 | pickle.dump(x,file) | x.__getstate__() |
序列化某对象 | pickle.dump(x,file) | x.__reduce__() |
序列化某对象(新picking协议) | pickle.dum(x,file,protoco_version) | x.__reduce_ex__(protocol_version) |
控制unpicking过程中对象的创建方式 | x = pickle.load(file) | x.__getnewargs__() |
在unpicking之后还原对象的状态 | x = pickle.load(file) | x.__setstate__() |
2.9 类的控制
目的 | 代码 | python实际调用 |
---|---|---|
类构造器 | x.Myclass() | x.__new__() |
类析构器 | del x | x.__del__() |
只定义特定集合的某些属性 | x.__slots__() | |
自定义散列值 | hash(x) | x.__hash__() |
获取某个属性的值 | x.color | type(x).__dict__[‘color’].__get__(x,type(x)) |
设置某个属性的值 | x.color = ‘PapayWhip’ | type(x).__dict__[‘color’].__set__(x,’PapayWhip’) |
删除某个属性 | del x.color | type(x).__dict__[‘color’].__del__(x) |
控制某个对象是否是该对象的实例 | isinstance(x,MyClass) | Myclass.__instancecheck__(x) |
控制某个类是否是该类的子类 | insubclass(c,MyClass) | Myclass.__subclasscheck__(c) |
控制某个类是否是该抽象基类的子类 | issubclass(c,MyABC) | MyABC.__subclasshook__(c) |
3、数据结构的操作
3.1 迭代
- 列表推导/列表生成
#将字符串转化成对应的ascll字符
symbols = 'abcdef'
#列表推导
codes = [ord(symbol) for symbol in symbols]
#列表生成
codes = tuplle(ord(symbol) for symbol in symbols)
- 字典推导
#将列表转化为字典
DIAL_CODES = [(86,'CHINA'),(91,'INDIA'),(1,'UNITED STATE')]
country_code = {country:code for code,country in DIAL_CODES}
- 集合推导
#把编码在32~255之间的字符的名字里有”SIGN“单词的挑出来,放在一个集合中
{chr(i) for i in range(32,256) if 'SIGN' in name(chr(i),'')}
3.2切片
-
一维数组/列表,无非记住一个规则
arr_name[start : end :step]
[:]
表示复制源列表负的index表示,从后往前。-1表示最后一个元素
-
二维(多维)数组的一般语法为
arr_name[行操作,列操作]
取行数据:
- arr[i,:] #取第i行数据 arr[i:j, :] #取第i行到第j行的数据
取列数据:
- arr[: ,0] #取第0列数据,以行的形式返回
- arr[:, :1] #取第0列数据,以列的形式返回
取数据块:
- arr[1:2, 1:3] #取第二行同时是第二列和地三列的元素
- arr[:, ::2] #取第一维的全部,按步长取第二维索引0到末尾之间的元素
3.3排序
3.3.1排序
fruits = ['grape','raspberry','apple','banana']
#list.sort方法
fruits.sort(key=len,reverse=False)
#内置的sorted方法
sorted(fruits,key=len)
3.3.2 用bisect来搜索
#bisect(haystack, needle):在haystack(干草垛)中搜索needle(针)的位置
import bisect
HAYSTACK = [1,4,5,6,7,12,20,21,23,26,29,30]
NEEDLES = [0,1,2,5,8,10,22,23,29,30,31]
ROW_FMT = '{0:2d} {1:2d} {2}{0:<2d}'
for needle in reversed(NEEDLES):
position = bisect(HAYSTACK,needle)
offset = position * ' |'
print(ROW_FMT.format(needle,position,offset))
3.3.3 用bisect.insort向有序表插入值
#insort可以保持有序序列的顺序
import bisect
import random
SIZE = 7
random.seed(1729)
my_list = []
for i in range(SIZE):
new_item = random.randrange(SIZE*2)
bisect.insort(my_lsit,new_item)
print('%2d ->' % new_item,my_lsit)
3.4拼接
3.4.1字符串拼接
#直接通过加号(+)操作符连接 website w = 'python' + 'tab' + '.com' #join方法 listStr = ['python', 'tab', '.com'] website = ''.join(listStr)
3.4.2 列表拼接
aList = [123, 'xyz', 'zara', 'abc'] aList.append(2009) aList.extend([2010]) "Updated List : {}".format(aList)
4.把函数当作对象
4.1闭包
#计算移动平均值
def make_averager():
count = 0
total = 0
def averager(new_value):
nonlocal count, total
count += 1
total += new_value
return total / count
return averager
#test
ave = make_averager()
ave(10)
ave(20)
4.2装饰器
#一个简单的装饰器,支持处理关键字参数
import time
DEFAULT_FMT = '[{elasped:0.8f}s] {name} {name}({args}) -> {result}'
def clock(fmt=DEFAULT_FMT):
def decorate(func):
def clocked(*_args):
t0 = time.time()
_result = func(*_args)
elasped = time.time() - t0
name = func.__name__
args = ', '.join(repr(arg) for arg in _args)
result = repr(_result)
print(fmt.format(**locals()))
return _result
return clocked
return decorate
@clock()
def snooze(seconds):
time.sleep(seconds)
for i in range(3):
snooze(.123)
4.3单分派泛函数
#functools.singledspatch装饰器可以把整体方案拆分为多个模块,使用@singledspatch装饰的普通函数会变为泛函数:根据第一个参数的类型,以不同的方式执行相同的操作的一组函数
from functools import singledspatch
from collections import abc
from numbers
from html
@singledspatch
def htmlize(obj):
content = html.escape(repr(obj))
return '<pre>{}</pre>'.format(content)
@htmlize.register(str)
def _(text):
content = html.escape(text).replace('\n', '<br>\n')
return '<p>{0}</p>'.format(content)
@htmlize.register(numbers.Integral)
def _(n):
return '<pre>{0} (0x{0:x})</pre>'.format(n)
@htmlize.register(tuple)
@htmlize.register(abc.MutableSequence)
def _(seq):
inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
return '<ul>\n<li>' + inner + '</li>\n</ul>
4.4参数化装饰器
#为了便于启用或禁用register执行的函数注册功能,我们为它提供了一个可选的active参数,设为False时,不注册被装饰的函数
registry = set()
def register(active=False):
def decorator(func):
print('running register(active=%s) -> decorator(%s)'
% (active,func))
if active:
registry.add(func)
else:
registry.discard(func)
return func
return decorator
@register(active=False)
def f1():
print('running f1')
@register(active=True)
def f2(active=True):
print('running f2')
def f3():
print('runing f3')
#test
registry
#为了接受新参数,新的register装饰器必须作为函数调用
register(active=True)(f3)
registry
register(active=False)(f2)
registry
5.自定义一个实现序列接口的类
自定义的类实现了以下功能:
- 基本的序列协议——__len__和__getitem__
- 正确表述拥有很多元素的实例
- 适当的切片支持,用于生成新的vector实例
- 综合各个元素的值计算散列值
- 自定义的格式语言扩展
#实现鸭子模型
from array import array
import math
class Vector2d:
typecode = 'd'
# def __init__(self,x,y):
# self.x = float(x)
# self.y = float(y)
#为了实现类可散列的特性,需要将类变量设成不可变类型也就是加上只读特性(1、设为私有,2、getter方法),最后实现可散列特性
#增加__hash__()
def __init__(self,x,y):
self.__x = float(x)
self.__y = float(y)
@property
def x(self):
return self.__x
@property
def y(self):
return self.__y
def __hash__(self):
return hash(self.x) ^ hash(self.y)
def __iter__(self):
return (i for i in (self.x,self.y))
def __repr__(self):
class_name = type(self).__name__
return '{}({!r},{!r})'.format(class_name,*self)
def __str__(self):
return str(tuple(self))
def __bytes__(self):
return (bytes([ord(self.typecode)])+bytes(array(self.typecode,self)))
def __eq__(self,other):
return tuple(self) == tuple(other)
def __abs__(self):
return math.hypot(self.x,self.y)
def __bool__(self):
return bool(abs(self))
#添加备选构造方法
@classmethod
def frombytes(cls,octets):
typecode = chr(octets[0])
memv = memoryview(octets[1:].cast(typecode))
return cls(*memv)
def ang(self):
return math.atan2(self.y,self.x)
#实现format方法
# def __format__(self,fmt_spec=''):
# components = (format(c,fmt_spec)for c in self)
# return '({},{})'.format(*components)
#对format方法拓展极坐标的表示方法
def __format__(self,fmt_spec=''):
if fmt_spec.endswith('p'):
fmt_spec = fmt_spec[:-1]
coords = (abs(self),self.ang())
outer_fmt = '<{},{}>'
else:
coords = self
outer_fmt = '({},{})'
components = (format(c,fmt_spec) for c in coords)
return outer_fmt.format(*components)
5.可迭代的对象、迭代器和生成器
迭代是数据处理的基石。扫描内存中方不下的数据集时,我们要找一种惰性获取数据项的方式,即按需一次获取一个数据项。这就是迭代器模式。
5.1可迭代对象
5.1.1序列可以迭代的原因
解释器需要迭代对象x时,会自动调用iter(x),内置的iter函数有以下作用:
- 检查对象是否实现了__iter__方法,如果实现了就调用它,获取一个迭代器。
- 如果没有实现__iter__方法,但是实现了__getitem__方法,python会创建一个迭代器,尝试按顺序(从索引0开始)获取元素
- 如果尝试失败,python抛出TypeError异常,通常会提示”C object is not iterable“,其中C是目标迭代对象
5.2 迭代器
5.2.1迭代器的接口
标准的迭代器接口有两个方法:
- __next__:返回下一个可用的元素,如果没有元素了,抛出StopIteration
- __iter__:返回self,以便在应该使用可迭代对象的地方使用迭代器,例如在for循环中
5.2.2 一个经典的迭代器
#可迭代对象和迭代器一定不能在一个对象中同时实现,一下为典型的迭代器
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self,text):
self.text = text
self.words = RE_WORD.findall(text)
def __iter__(self):
return SentenceIterator(self.words)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
#实现迭代器
class SentenceIterator(self,words):
def __init__(self,words):
self.words = words
self.index = 0
def __next__(self):
try:
word = self.words[self.index]
except IndexError:
raise StopIteration()
self.index += 1
return word
def __iter__(self):
return self
5.2.3为什么不能把迭代对象同时变成迭代器(添加__next__()方法)
《设计模式:可复用面向对象软件的基础》一书认为:
迭代器模式可以用来:
- 访问一个聚合对象的内容而无需暴露它的内部表示
- 支持对聚合对象的多种遍历
- 为遍历不同的聚合结构提供统一的接口(即支持多态迭代)
为了”支持多种遍历“,必须能从同一个可迭代的实例中获取多个独立的迭代器,而且各个迭代器要能维护自身的内部状态,因此这一模式正确的实现方式是,每次调用iter(my_iterable)都新建一个独立的迭代器,这就是为什么可迭代对象一定不能是自身的迭代器。也就是说,可迭代的对象必须实现 __iter__方法,但不能实现__next__方法
5.2.4 用生成器函数代替迭代器
只要python函数的定义体中有yield关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象,也就是说,生成器函数就是生成器工厂。
#使用生成器yield代替SentenceIterator类
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self,text):
self.text = text
self.words = RE_WORD.findall(text)
def __iter__(self):
for word in self.words:
yield word
return
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
5.2.5 使用生成器表达式改进生成器函数,并且惰性实现
#使用生成器表达式简化生成器函数,并且对__iter__函数惰性实现
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self,text):
self.text = text
def __iter__(self):
return (match.group() for match in RE_WORD.finditer(self.text))
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
5.3 标准库中的生成器函数
5.3.1 过滤功能
从输入的可迭代对象中产出元素的子集,而且不修改元素本身
模块 | 函数 | 说明 |
---|---|---|
itertools | compress(it, selector_it) | 并行处理两个可迭代的对象;如果selector_it中的元素是真值,产出it中对应的元素 |
itertools | dropwhile(predicate,it) | 处理it,跳过predictate的计算结果为真值的元素,然后产出剩下的各个元素(从第一个False值之后不再进一步检查) |
(内置) | filter(predicate,it) | 把it中的各个元素传给predicate,如果predicate(item)返回真值,那么产出对应的元素,如果predicate是None,那么只产出对应的元素 |
itertools | filterfalse(predicate,it) | 与filter函数的作用类似,不过predicate的逻辑是相反的;predicate返回假值时产出对应的元素 |
itertools | islice(it, stop)或isclice(it,start,stop,step=1) | 产出it的切片,作用类似于s[:stop]或s[start:stop:step],不过it可以是任何可迭代的对象,而且这个函数实现的是惰性操作 |
itertools | takewhile(predicate, it) | predicate返回真值时产出对应的元素,然后立即停止,不再继续检查 |
5.3.2用于映射的生成器函数
模块 | 函数 | 说明 |
---|---|---|
itertools | accumulate(it, [func]) | 产出累计的总和;如果提供了func,那么把前两个元素传给它,然后把计算结果和下一个元素传给它,一次类推,最后产出结果 |
(内置) | enumerate(iterable,start =0) | 产出由两个元素组成的元祖,结构是(index, item),其中index从startt开始计数,item则从iterable中获取 |
(内置) | map(func,it1,[it2,…,itN]) | 把it中的各个元素传给func,产出结果;如果传入N个可迭代的对象,那么func必须能接受N个参数,而且要并行处理各个可迭代对象 |
itertools | starmap(func, it) | 把it中的各个元素传给func,产出结果;输入的可迭代对象应该产出可迭代的元素iit,然后func(*iit)这种形式调用func |
5.3.3合并多个可迭代对象的生成器函数
模块 | 函数 | 说明 |
---|---|---|
itertools | chain(it1,…,itN) | 先产出it1中的所有元素,然后产出it2中的所有元素,一次类推,无缝连接在一起 |
itertools | chain.from_iterable(it) | 产出it生成的各个可迭代对象的元素,一个接一个,无缝连接在一起;it应该产出可迭代的元素,例如可迭代的对象列表 |
itertools | product(it1,…,itN,repeat=1) | 计算笛卡尔积:从输入的各个可迭代对象中获取元素,合并成由N个元素组成的元祖,与嵌套的for循环效果一样;repeat指明重复处理多少次输入的可迭代对象 |
(内置) | zip(it1,…,itN) | 并行从输入的各个可迭代对象中获取元素,产出由N个元素组成的元祖,只要有一个可迭代的对象到头了,就默默地停止 |
itertools | zip_longest(it1,…,itN,fillvalue=None) | 并行从输入的各个可迭代对象中获取元素,产出由N个元素组成的元祖,等到最长的可迭代对象到头后才停止,空缺的值使用fillvalue填充 |
5.3.4把输入的各个元素扩展成多个输出元素的生成器函数
模块 | 函数 | 说明 |
---|---|---|
itertools | combinations(it, out_len) | 把it产出的out_len个元素组合在一起,然后产出 |
itertools | combinations_with_replacement(it, out_len) | 把it产出的out_len个元素组合在一起,然后产出,包含相同的元素组合 |
itertools | count(start = 0, step =1) | 从start开始不断产出数字,按step指定的步幅增加 |
itertools | cycle(it) | 从it中产出各个元素,存储各个元素的副本,然后按顺序重复不断的产出各个元素 |
itertools | permutations(it, out_len=None) | 把out_len个it产出的元素排列在一起,然后产出这些排列;out_len的默认值等于len(list(it)) |
itertools | repeat(item, [items]) | 重复不断产出指定的元素,除非提供times,指定次数 |
5.3.5 用于重新排列元素的生成器函数
模块 | 函数 | 说明 |
---|---|---|
itertools | groupby(it, key=None) | 产出由两个元素组成的元素,形式为(key, group),其中key是分组标准,group是生成器,用于产出分组中的元素 |
(内置) | reversed(seq) | 从后向前,倒序产出seq中的元素,seq必须是序列,或者是实现了__reversed__特殊方法的对象 |
itertools | tee(it, n=2) | 产出一个由n个生成器组成的元祖,每个生成器用于单独产出输入的可迭代对象中的元素 |
5.3.6可迭代的规约函数
模块 | 函数 | 说明 |
---|---|---|
(内置) | all(it) | it中的所有元素都为真值时返回True,否则返回False,all([])返回True |
(内置) | any(it) | 只要it中的元素为真值就返回True,否则返回False,any([])返回false |
(内置) | max(it, [key=,][default=]) | 返回it中最大的元素,*key是排序函数,与sorted函数中的一样,如果可迭代的对象为空,返回default |
(内置) | min(it, [key=,][default=]) | 返回it中最小的元素,*key是排序函数,与sorted函数中的一样,如果可迭代的对象为空,返回default |
functools | reduce(func,it,[initial]) | 把前两个元素传给func,然后把计算结果和第三个元素传给func,以此类推,返回最后的结果,如果提供了initial,把它当作第一个元素传入 |
(内置) | sum(it,start=0) | it中的所有元素的总和,如果提供可选的start,会把它加上(计算浮点数的加法时,可以使用math.fsum函数提高精度) |
6.协程
6.1把生成器当作协程
python2.2引入yield关键字实现了的生成器函数,python2.5中为生成器对象添加了额外的方法和功能,其中最值得关注的是.send()方法。与__next__()方法一样,.send()方法致使生成器前进到下一个yield语句。不过.send()方法还允许使用生成器的客户把数据发给自己,即不管传给.send()方法什么参数,那个参数都会成为生成器函数定义体中对应的yield表达式的值。也就是说,.send()方法允许客户代码和生成器之间双向交换数据。而__next__()方法只允许客户从生成器中获取数据。
这项重要的“改进”甚至改变了生成器的本性:像这样使用的话,生成器就变身成为协程,两者之间的需要注意一下几点:
- 生成器用于生成供迭代的数据
- 协程是数据的消费者
- 为了避免脑袋炸裂,不能把两个概念混为一谈
- 协程与迭代无关
- 注意,虽然在协程中会使用yield产出值,但这与迭代无关
6.2 yield关键词在协程中的作用
python中,yield关键字有两种释义:产出和让步。对于python生成器中的yield来说,这两个含义都成立。yield item这行代码会产出一个值,提供给next(…)的调用方;此外,还会做出让步,暂停执行生成器,让调用方继续工作,知道需要使用另一个值时再调用next()。调用方会从生成器中拉取值。
协程和生成器对yield关键字的使用方法不同,协程中yield通常出现在表达式的右边 (例如,datum = yield),可以产出值,也可以不产出——如果yield关键字后面没有表达式,那么生成器产出None。协程可能会从调用方接收数据,不过调用方把数据提供给协程使用的方法是.send(datum)方法,而不是next(…)函数。通常,调用方会把值推送给协程。
#使用协程实现记忆性计算平均值的函数
def average():
total = 0.0
count = 0
average = None
while True:
msg = ''
try:
term = yield average
except ZeroDivisionError:
msg = 'Please enter number'
else:
total += term
count += 1
average = total/count
6.3 yiled from句法
6.3.1 生成器中的yield from语句
#yield from可以简化for循环中的yield表达式,例如:
def gen():
for c in 'AB':
yield c
for i in range(1,3):
yield i
#可以改写为:
def gen():
yield from 'AB'
yield from range(1,3)
#使用yield from链接可迭代的对象
def chain(*iterables):
for it in iterables:
yield from it
6.3.2 协程中的 yield from句法
yield from:把职责委托给子生成器的句法,使用它可以把复杂的生成器重构成小型的嵌套生成器,省去了之前把生产器的工作委托给子生成器所需的大量样本代码。在协程中,yield from的主要功能是打开双向通道,把最外层的调用方与最内层的子生成器连接起来,这样二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程中添加大量处理异常的样板代码。有了这个结构,协程可以通过以前不可能的方式委托职责。以下是在协程中使用yield from语句需要了解的新的术语:
- 委派生成器
- 包含yield from
表达式的生成器函数,委派生成器在yield from表达式处暂停时,调用方可以直接把数据发给子生成器,子生成器再把产出的值发给调用法。子生成器返回之后,解释器会抛出StopIteration异常,并把返回值附加在异常对象上,此时委派生成器会恢复 - 子生成器:
- 从yield from表达式中
部分获取的生成器。 - 调用方
- 委派生成器的客户端代码
from collections import namedtuple
Result = namedtuple('Result','count average')
#子生成器
def averager():
average = None
total = 0.0
count = 0
while True:
item = yield
if item is None:
break
total += item
count += 1
average = total/count
return Result(count,average)
#委派生成器
```
grouper发送的每个值都会经yield from处理,通过管道传给averager实例。grouper会在yield from表达式处暂停,等待averager实例处理客户端发来的值。averager实例运行完毕后,返回的值绑定到results[key]上。while循环会不断创建averager实例,处理更多的值
```
def grouper(results,key):
while True:
results[key] = yield from averager()
#调用方
def main(data):
results = {}
for key,values in data.items():
group = grouper(results,key)
next(group)
for value in values:
group.send(value)
group.send(None)
print(results)
def report(results):
for key, result in sorted(results.items()):
group, unit = key.split(';')
print('{:2} {:5} averaging {:.2f}{}'.format(
result.count,group,result.average,unit
))
data = {
'girls;kg':
[40.9,38.5,44.3,42.2,45.2,41.7,44.5,38,40.6,44.5],
'girls;m':
[1.6,1.51,1.4,1.3,1.41,1.39,1.33,1.46,1.45,1.43],
'boys;kg':
[39.0,40.8,43.2,40.8,43.1,38.6,41.4,40.6,36.3],
'boys;m':
[1.38,1.5,1.32,1.25,1.37,1.48,1.25,1.49,1.46]
}
if __name__ == '__main__':
main(data)
6.4 协程中异常的处理
协程中未处理的异常会向上冒泡,传给next函数或者send方法的调用法(即触发协程的对象),终止协程的一种方式是:发送某个哨符值,让协程退出。内置的None常量经常用作哨符值。从python2.5开始,客户代码可以在生成器对象上调用两个方法,显式地把异常发给协程。
- geneator.throw(exc_type[, exc_value[, trackback]])
- 致使生成器在暂停的yield表达式处抛出指定的异常。如果生成器处理了抛出的异常,代码会向前执行到下一个yield表达式,而产出的值会成为调用generator.throw方法得到的返回值。如果生成器没有处理抛出的异常,异常会向上冒泡,传到调用方的上下文中
- generator.close()
- 致使生成器在暂停的yield表达式处抛出GeneratorExit异常。如果生成器没有处理这个异常,或者抛出了StopIteration异常(通常指运行到结尾),调用方不会不错。如果收到GeneratorExit,生成器一定不能产出值,否则解释器抛出RuntimeError异常。生成器抛出的其他异常会向上冒泡,传给调用方。另外如果无法处理传入的异常,协程会停止,即状态变成‘GEN_CLOSED’。
6.4.1 yield from句法结构对异常的处理
yield from结构会在内部自动捕获stopIteration异常。这种处理方式与for循环处理StopIteration异常的方式一样:循环机制使用使用易于理解的方式处理异常。对于yield from结构来说,解释器不仅会捕获StopIteration异常,还会把value属性的值变为yield from表达式的值。具体来说:
- 子生成器产出的值都直接传给委派生成器的调用方(即客户端代码)
- 使用send()方法发给委派生成器的值都直接传给子生成器。如果发送的值是None,那么会调用子生成器的__next__()方法。如果发送的值不是None,那么会调用子生成器的send()方法。如果调用的方法抛出StopIteration异常,那么委派生成器恢复运行。任何其他异常都会向上冒泡,传给委派生成器。
- 生成器退出时,生成器(或子生产器)中的return expr表达式会触发StopIteration(expr)异常抛出
- yield from表达式的只是子生成器终止时传给StopIteration异常的第一个参数
yield from结构的另外两个特性与异常和终止有关
- 传入委派生成器的异常,除了GeneratorExit之外都传给子生成器的throw()方法。如果调用throw()方法时抛出StopIteration异常,委派生成器恢复运行。StopIteration之外的异常会向上冒泡,传给委派生成器。
- 如果把GeneratorExit异常传入委派生成器,或者在委派生成器上调用close()方法,那么在子生成器上调用close()方法,如果它有的话。如果调用close()方法导致异常抛出,那么异常也会向上冒泡,传给委派生成器;否则,委派生成器会抛出GeneratorExit异常
参考文献
https://blog.csdn.net/Airuio/article/details/80417569
[流畅的python.Luciano Ramalho著.安道 吴珂译][https://github.com/fluentpython/example-code]