• 《Fluent Python》 CH.14_控制流程_可迭代的对象、迭代器 和生成器 (yield关键字、iter函数、生成器工作原理)


    小结

    • 共计 61页

    本章速读

    补充知识点

    • close
      生成器的 close 方法也比较简单,就是手动关闭这个生成器,关闭后的生成器无法再进行操作。
    g.close()
    
    • yield关键字的正确使用&适用场景
      • 超大集合(超出物理机的内存上限)的生成,生成器只有在执行到 yield 时才会迭代数据,这时只会申请需要返回元素的内存空间,后面的内存占用可以得到释放
      • 简化代码结构:如果一个方法要返回一个 list,但这个 list 是多个逻辑块组合后才能产生的
      • 协程与并发,多线程的方式编写程序代码,最常用的编程模型就是「生产者-消费者」模型,即一个进程 / 线程生产数据,其他进程 / 线程消费数据。

    引用自: Python进阶——如何正确使用yield?
    Kaito,https://zhuanlan.zhihu.com/p/321302488

    14.1 Sentence类第1版:单词序列

    序列可以迭代的原因:iter函数

    解释器需要迭代对象 x 时,会自动调用 iter(x)。

    内置的 iter函数有以下作用。

    (1) 检查对象是否实现了 iter 方法,如果实现了就调用它,获取 一个迭代器。

    (2) 如果没有实现 iter 方法,但是实现了 getitem 方法, Python 会创建一个迭代器,尝试按顺序(从索引 0 开始)获取元素。

    (3) 如果尝试失败,Python 抛出 TypeError 异常,通常会提示“C object is not iterable”(C 对象不可迭代),其中 C 是目标对象所属的类。

    14.2 可迭代的对象与迭代器的对比

    可迭代的对象

    • 使用 iter 内置函数可以获取迭代器的对象。
    • 如果对象实现了能返 回迭代器的 __iter__ 方法,那么对象就是可迭代的。
    • 序列都可以迭代;实现了 __getitem__ 方法,而且其参数是从零开始的索引,这种对象也可以迭代。

    可迭代的对象和迭代器之间的关系:

    Python 从可迭代的对象中获取迭代器

    使用for/while循环简单模拟迭代器

    下面是一个简单的 for 循环,迭代一个字符串。这里,字符串 'ABC' 是可迭代的对象。背后是有迭代器的,只不过我们看不到:

    s = 'ABC'
    for char in s:
        print(char)
    
    A
    B
    C
    
    it = iter(s)
    while True:
        try:
            print(next(it))
        except StopIteration:
            del it
            break
    
    A
    B
    C
    

    StopIteration 异常表明迭代器到头了。Python 语言内部会处理 for 循环和其他迭代上下文(如列表推导、元组拆包,等等)中的 StopIteration 异常。

    标准的迭代器接口中有两个方法:

    • next

    • iter

    14.4 Sentence类第3版:生成器函数

    • 生成器函数都不会抛出 StopIteration 异常,而是在生成完全部值之后会直接退出
    import re
    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
    
    # 测试
    sen = Sentence('233 2333')
    gentor = sen.__iter__()
    
    print(next(gentor)) # 233
    print(next(gentor)) # 2333
    print(next(gentor)) # StopIteration
    print(next(gentor))
    
    233
    2333
    
    
    
    ---------------------------------------------------------------------------
    
    StopIteration                             Traceback (most recent call last)
    
    <ipython-input-8-baaa7e5b37df> in <module>
          1 print(next(gentor))
          2 print(next(gentor))
    ----> 3 print(next(gentor))
          4 print(next(gentor))
    
    
    StopIteration: 
    

    生成器函数的工作原理

    只要 Python 函数的定义体中有 yield 关键字,该函数就是生成器函 数。

    调用生成器函数时,会返回一个生成器对象。

    也就是说,生成器函 数是生成器工厂。

    有时,我会在生成器函数的名称中加上 gen 前缀或后缀,不过这不是习惯做法。显然,如果 实现的是迭代器,那就不能这么做,因为所需的特殊方法必须命名为 iter。 -- Guido

    def gen_123():
        yield 1
        yield 2
        yield 3
    
    gen_123()
    
    <generator object gen_123 at 0x0000020B047D5EC8>
    
    g = gen_123()
    print(next(g))
    print(next(g))
    print(next(g))
    print(next(g))
    
    1
    2
    3
    
    
    
    ---------------------------------------------------------------------------
    
    StopIteration                             Traceback (most recent call last)
    
    <ipython-input-13-7295ea4d5aaf> in <module>
          3 print(next(g))
          4 print(next(g))
    ----> 5 print(next(g))
          6 
    
    
    StopIteration: 
    

    简单裂解生成器 (yield暂停,调用一次暂停一次)

    生成器函数会创建一个生成器对象,包装生成器函数的定义体。把生成 器传给 next(...) 函数时,生成器函数会向前,执行函数定义体中的 下一个 yield 语句,返回产出的值,并在函数定义体的当前位置暂 停。最终,函数的定义体返回时,外层的生成器对象会抛出 StopIteration 异常——这一点与迭代器协议一致。

    使用 for 循环更清楚地说明了生成器函数定义体的执行过 程。

    按需惰性生成元素,每次调用再生成一次

    def gen_nums():
        print('start...')
        yield 'A'
        print('continue')
        yield 'B'
        print('end.')
    
    for c in gen_nums():
        print('-->', c)
    
    start...
    --> A
    continue
    --> B
    end.
    

    14.5 Sentence类第4版:惰性实现

    import re
    RE_WORD = re.compile('w+')
    
    class Sentence:
        def __init__(self, text):
            self.text = text
    
        def __iter__(self):
            for match in RE_WORD.findall(self.text):
                yield match.group() # match.group() 方法从 MatchObject 实例中提取匹配正则表达式的 具体文本。
    
    
    sen = Sentence('a b c d  e f g')
    for x in sen:
        print(x)
    
    
    a
    b
    c
    d
    e
    f
    g
    

    14.6 Sentence类第5版:生成器表达式

    如果列表推导是 制造列表的工厂,那么生成器表达式就是制造生成器的工厂

    def gen_nums():
        print('start...')
        yield 'A'
        print('continue')
        yield 'B'
        print('end.')
    
    gen_lst = [x*3 for x in gen_nums()]
    
    start...
    continue
    end.
    
    for i in gen_lst:
        print(i)
    
    
    AAA
    BBB
    

    14.7 何时使用生成器表达式

    见小结的补充.

    14.8 另一个示例:等差数列生成器

    使用itertools模块生成等差数列

    例如,itertools.count 函数返回的生成器能生成多个数。

    如果不传 入参数,itertools.count 函数会生成从零开始的整数数列。不过, 我们可以提供可选的 start 和 step 值,这样实现的作用与 aritprog_gen 函数十分相似:

    import itertools
    gen = itertools.count(1, .5)
    next(gen)
    
    1
    
    next(gen)
    
    
    1.5
    
    # list(count()) 是个大坑
    
    

    不过,itertools.takewhile 函数则不同,它会生成一个使用另一个 生成器的生成器,在指定的条件计算结果为 False 时停止。因此,可 以把这两个函数结合在一起使用,编写下述代码:

    gen = itertools.takewhile(lambda n: n < 3, itertools.count(1, .5))
    list(gen)
    
    
    [1, 1.5, 2.0, 2.5]
    

    14.9 标准库中的生成器函数

    • 有用于逐行迭代纯文本文件的对象,还有出 色的 os.walk 函数;这个函数在遍历目录树的过程中产出文件名,因此递归搜索文件系统像for循环那样简单。
    • reverse(),返回的也是个生成器
    • itertools.groupby(it, key=None); 产出由两个元素组成的元素,形式为 (key, group),其中 key 是分组标准,group 是生成器, 用于产出分组里的元素
      更多,略。

    14.10 Python 3.3中新出现的句法:yield from

    类似于语法糖,yield from i 完全代替了内层的 for 循环;但是在后面的章节中,还可以yield from 还会创建通道,把内层生成器直接与外层生成器的客户端联系起来。把生成器当成协程使用时,这个 通道特别重要,不仅能为客户端代码生成值,还能使用客户端代码提供 的值。

    如果生成器函数需要产出另一个生成器生成的值,传统的解决方法是使 用嵌套的 for 循环。

    例如,下面是我们自己实现的 chain 生成器:

    def chain(*iterables):
        for it in iterables:
            for i in it:
                yield i
    s = 'ABC'
    t = tuple(range(3))
    list(chain(s, t))
    
    ['A', 'B', 'C', 0, 1, 2]
    
    def chain(*iterables):
        for i in iterables:
            yield from i
    
    list(chain(s, t))
    
    
    ['A', 'B', 'C', 0, 1, 2]
    

    14.11 可迭代的归约函数(类似于reduce的实现之一)

    表 14-6 中的函数都接受一个可迭代的对象,然后返回单个结果。这些 函数叫“归约”函数、“合拢”函数或“累加”函数。其实,这里列出的每个 内置函数都可以使用 functools.reduce 函数实现,内置是因为使用 它们便于解决常见的问题。

    表14-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 函数提高 精度

    其他的:

    • sorted 会构建并 返回真正的列表

    14.12 深入分析iter函数 (使用哨符,掷骰子)

    • iter 函数还有一个鲜为人知的用法:传入两个参数,使用常规 的函数或任何可调用的对象创建迭代器。
    • 这样使用时,第一个参数必须 是可调用的对象,用于不断调用(没有参数),产出各个值;第二个值 是哨符,这是个标记值,当可调用的对象返回这个值时,触发迭代器抛 出 StopIteration 异常,而不产出哨符。

    下述示例展示如何使用 iter 函数掷骰子,直到掷出 1 点为止:

    from random import randint
    def d6():
        return randint(1, 6) # Return random integer in range [a, b], including both end points.
    d6_iter = iter(d6, 6)
    d6_iter
    
    <callable_iterator at 0x20b03d96788>
    
    for roll in d6_iter:
        print(roll)
    
    
    4
    3
    2
    

    14.14 把生成器当成协程——允许双方交换数据

    与 .next() 方法一样,.send() 方法致使生成器前进到下一个 yield 语句。不过,.send() 方法还允许使用生成器的客户把数据发给 自己,即不管传给 .send() 方法什么参数,那个参数都会成为生成器 函数定义体中对应的 yield 表达式的值。

    也就是说,.send() 方法允许在客户代码和生成器之间双向交换数据。而 .next() 方法只允许客户从生成器中获取数据。

    这是一项重要的“改进”,甚至改变了生成器的本性:像这样使用的话, 生成器就变身为协程。在 PyCon US 2009 期间举办的一场著名的课程中 (http://www.dabeaz.com/coroutines/),David Beazley(可能是 Python 社 区中在协程方面最多产的作者和演讲者)提醒道:

    • 生成器用于生成供迭代的数据
    • 协程是数据的消费者
    • 为了避免脑袋炸裂,不能把这两个概念混为一谈
    • 协程与迭代无关

    注意,虽然在协程中会使用 yield 产出值,但这与迭代无关。

    第 16 章会讨论协程。

    你不逼自己一把,你永远都不知道自己有多优秀!只有经历了一些事,你才会懂得好好珍惜眼前的时光!
  • 相关阅读:
    函数特化
    模板函数总结
    学习代码1
    宏指令
    #define宏作用
    oracle 重要函数
    JMeter 系列之—-01 使用
    Selenium系列之--03 常见元素操作总结
    【转】TestNG常用注解
    CMMI 2,3,4,5级涉及的过程域(PA)介绍
  • 原文地址:https://www.cnblogs.com/zhazhaacmer/p/14464790.html
Copyright © 2020-2023  润新知