如何理解Python中的生成器

转载请注明出处:https://www.jianshu.com/u/5e6f798c903a
[^*] 表示注脚,在文末可以查看对应连接,但简书不支持该语法。

generator [^4] [^7] [^9]

本质上来说,生成器(generator)就是一个函数,它提供了一种实现迭代器协议的便捷方式。生成器与普通函数的区别在于它包含 yield 表达式,并且不需要定义 __iter__()__next__()。也就是说,使用 yield 语句的函数或方法便可被称为生成器函数( generator function )。关于 yield 可参考 The yield statementthe documentation for the yield expression.

# 一个生成器函数的示例:
def generator_func():
    cont = 0
    while cont < 3:
        cont += 1
        yield cont

generator 在官方文档的语境中存在如下两种含义:

  • 第一种用于指代生成器函数,本文内容均采用这种含义

  • 第二种是用于指代 generator iterator(下一节将介绍此概念)。比如通过下面的代码可以看到 Python 将生成器函数描述为函数类型,但将 generator iterator 描述为 Generator 类型。因此,当我们在文档中遇到 generator 时,一定要根据上下文来判断其具体所指的对象。

    # 关于生成器在不同语境中的含义:
    from collections import abc
    import types
    def generator_func():
        cont = 0
        while cont < 3:
            cont += 1
            yield cont
    
    
    g_iterator = generator_func()
    assert isinstance(generator_func, types.FunctionType) # 无异常
    assert isinstance(g_iterator, abc.Generator) # 无异常
    

1. generator iterator

generator-iterator 是通过生成器函数创建的实例对象 (下文中简称为 g_iterator),该对象支持 __iter__()__next__() 方法,属于迭代器。g_iterator 适用于任何需要迭代器的场景。注意,通过生成器函数创建 g_iterator 时,并不会执行生成器函数体中的任何代码:

def generator_func():
    print("生成器函数")
    cont = 0
    while cont < 3:
        cont += 1
        yield cont


g_iterator = generator_func()
# 不会打印"生成器函数"

细节上来讲,g_iterator 用于执行函数体:通过调用 g_iterator.__next__() 方法,可继续执行函数体。如果在执行过程中遇到了 yield 语句,便会暂停执行并返回指定变量;如果在遇到 return 语句或者函数体已执行完毕时,便会抛出 StopIteration ,表明 g_iterator 对象已被耗尽。

简单来说,yield 用于暂停 g_iterator 的执行,并会记住当前位置的执行状态(包括局部变量和待处理的 try 语句)。当 g_iterator 恢复执行时,便会从之前中断的位置开始执行(普通函数每次调用时,都会从头开始重新执行)

如果将 g_iterator 对象传递给 iter(),只会返回指向 g_iterator 自身的引用,并不会创建具备新id的迭代器对象。

class Generator:
    def __iter__(self):#生成器函数
        cont = 0
        while cont < 3:
            cont += 1
            yield cont


a_generator = Generator()

# 通过同一个generator可创建不同的generator iterator。
# 例如g_iter1和g_iter2是两个具备不同标识符id的generator iterator
g_iter1 = iter(a_generator)
g_iter2 = a_generator.__iter__()
print("g_iter1的id是:{0}, 类型是:{1}".format(id(g_iter1), type(g_iter1)))
print("g_iter2的id是:{0}, 类型是:{1}\n".format(id(g_iter2), type(g_iter2)))

# 对同一个generator iterator依次执行这两种方法,均返回带有同一id的对象
print(id(g_iter1.__iter__()))
print(id(iter(g_iter1)))

"""输出:
g_iter1的id是:2742184172272, 类型是:<class 'generator'>
g_iter2的id是:2742184172624, 类型是:<class 'generator'>

2742184172272
2742184172272
"""

2. 生成器表达式

generator expression[^4]

该表达式同样会返回一个 generator iterator。该表达式与常规表达式一样,会使用 for 循环来定义一个循环变量 i 及其范围;也可使用 ifi 进行筛选。该组合表达式可为封闭(enclosing)函数生成值:

sum(i*i for i in range(10))  # sum of squares 0, 1, 4, ... 81; result 285

2.1 对比其他常规表达式

列表推导式用于构建一个列表:

>>> numbers = [1, 2, 3, 4, 5, 6]
>>> [x * x for x in numbers]
[1, 4, 9, 16, 25, 36]

集合推导式用于构建一个集合:

>>> {x * x for x in numbers}
{1, 4, 36, 9, 16, 25}

字典推导式用于构建一个字典:

>>> {x: x * x for x in numbers}
{1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36}

注意,没有元组推导式,元括弧用于创建生成器表达式:

>>> lazy_squares = (x * x for x in numbers)
>>> lazy_squares
<generator object <genexpr> at 0x10d1f5510>
>>> next(lazy_squares)
1
>>> list(lazy_squares)
[4, 9, 16, 25, 36]

注脚:
[1] 语言参考 – 3.1. Objects, values and types
[2] 标准库 8.4. collections.abc — Abstract Base Classes for Containers
[3] Iterables vs. Iterators vs. Generators | 完全理解 Python 迭代对象、迭代器、生成器
[4] Glossary 术语表
[5] iter(object[, sentinel])
[6] 语言参考 – 8.3. The for statement
[7] 标准库 – 4.5. terator Types
[8] 语言参考 – 3.3.7. Emulating container types
[9] 语言参考 –3.2. The standard type hierarchy

    原文作者:Orca_J35
    原文地址: https://www.jianshu.com/p/fb9148cfb332
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞