如何理解Python中的生成器

作者: import_hello | 来源:发表于2018-08-05 18:40 被阅读5次

    转载请注明出处: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

    相关文章

      网友评论

        本文标题:如何理解Python中的生成器

        本文链接:https://www.haomeiwen.com/subject/dnabvftx.html