henryzhou Make robot converse with human naturally

流畅的python笔记

2018-07-16
Henryzhou

[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]


上一篇 深度学习小记

Comments

Content