Python生成器

关于生成器的一点笔记


概念

生成器,其本质是在数据需要的时候数据才出现,其他时候待命就好。

如此一样,原本大块的数据,得并非同时需要的数据,是可以被分割成一块块的小数据,这样减去了数据所占用内存空间,也不用消耗计算机提前计算一大块数据的时间。

使用yield语句构建生成器,书上说的使用StopIteration异常来终止生成器,并且不会生成回溯,仅仅是在python2的解释器上。

python3.7中,StopIteration会生成回溯,并提示错误RuntimeError: generator raised StopIteration而同一份代码在python2则会不报错。

另外,在pyton2中不能使用return来终止生成器,并且还会报错。而在python3中可以使用return来终止生成器,不报错。

建议使用StopIteration来终结生成器,因为其只在python3.7中才会报错,即3+版本还是能正常运行的。如果是使用了循环,那可是用if elsebreak语句搭配来终止生成器,其可在版本2和版本3中运行。


使用示例


demo1
def generation():
    yield 1
    yield 2
    yield 3
    yield 4


if __name__ == '__main__':
    a = [i for i in generation()]
    print(a)  # 运行结果 [1, 2, 3, 4]

demo2

生成器是迭代器的一种,迭代对象则是任何定义了__iter__方法的对象。
可迭代对象的__iter__方法负责返回一个迭代器。
Python中的迭代器则是包含__next__方法的任何对象,因此,它们能响应next函数

生成器可以是迭代器,但不一定是迭代对象

In [2]: def generation():
   ...:     yield 1
   ...:     yield 2
   ...:     

In [3]: a = generation()

In [4]: next(a)
Out[4]: 1

In [5]: next(a)
Out[5]: 2

In [6]: next(a)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-6-15841f3f11d4> in <module>()
----> 1 next(a)

StopIteration: 

如上所示,生成器是能响应next函数的,直到没有值后自动报错提示StopIteration
那普通的range函数可以吗?

In [8]: a = range(0, 5)

In [9]: next(a)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-9-15841f3f11d4> in <module>()
----> 1 next(a)

TypeError: 'range' object is not an iterator

In [10]: b = iter(a)

In [11]: next(b)
Out[11]: 0

In [12]: next(b)
Out[12]: 1

In [13]: a
Out[13]: range(0, 5)

In [14]: type(a)
Out[14]: range

In [15]: type(b)
Out[15]: range_iterator

In [16]: b
Out[16]: <range_iterator at 0x7f89b6b48480>

答案是:不可以。range返回的是range的对象,其是迭代对象,不能响应next方法,仅能响应__iter__方法,经过iter处理后,便能响应next函数了


demo3

生成器的宗旨是避免造成不必要的浪费*,无论是浪费内存,还是浪费计算时间,都是时间和空间上的一种浪费。
如果本来没有必要存在的数据还存在就是一种浪费,如下代码所示

def fibonacci():
    nums = []
    while True:
        if len(nums) < 2:
            nums.append(1)
        else:
            nums.append(sum(nums))
        yield nums[-1]
# 斐波那契数据如果只需要最后一个数据的话,那之前就没有存在必要
# 从此来看,nums中只是不断的放数据,随着次数的增多,即无穷正数最终会令内存挂掉
def fibonacci():
    nums = []
    while True:
        if len(nums) < 2:
            nums.append(1)
        else:
            nums.append(sum(nums))
            nums.pop(0)
        yield nums[-1]
# 这是上一个版本的改良版,即nums最多只是一个包含两个数的列表
# 除于一开始两个数不谈,往后的最新值添加,nums有三个数
# 根据斐波那契数列的规律,只需要前两个数相加就足够了,那么,第一个值就可以弹出
# 从而不浪费内存空间

demo4

生成器有一个send方法,其允许生成器的反向沟通。
因为yield语句实际上是一个表达式,其有返回值就是None,而send方法给出的值则是此次yield表达式的结果。并且,在send方法调用的时候,yield语句已经再次运行了一次,即当send方法使用过后,看到的值已经是提供给send方法计算一次之后的值了

def squares():
    cursor = 1
    while True:
        response = yield cursor ** 2  # 1
        if response is not None:  # 2
            cursor = int(response)  # 3
        else:
            cursor += 1


if __name__ == '__main__':
    s = squares()
    print(next(s))
    print(next(s))
    print(s.send(7))
    print(next(s))
"""
运行结果
1
4
49
64
如果注释了 print send,直接使用send(7),那么看到的值是1 4 64,send 的值会立刻影响计算的值并返回
"""

作用过程:

  • 启用send()之后,值即该传给成器,步骤1是这样response = 7
  • 步骤2此次为真,cursor值得到更新cursor = 7,步骤3完成,回到步骤1
  • 再次执行yield curosr ** 2就能得到结果49

过程理解:send()---> (give - get - calc - return(next))


何时编写生成器

1.分块访问数据
2.分块计算数据


基本原则:通过自己的代码提前去做一系列工作或存储一堆数据是没有好处的。

分块访问数据

分块访问数据,即需要访问的数据量在,但是其并非需要对数据量的整体做操作的时候,是不需要将其这么大的数据存入内存,如文件读写,斐波那契数列一样。那么就需要将数据分开处理,每次处理一点,直到处理完毕。

分块计算数据

如序列的计算,因存在于无穷数,所以某种情况下序列是无穷的,那即不可能用列表表示,但生成器却可以,其只在用时才计算的原则将不会过多的占用计算机资源,只要在合适的情况下终止生成器即可。


特殊语法

yield from:适用于生成器调用另一个生成器

def generation1():
    yield 1
    yield 2
    yield 3
    yield 4
    yield 5


def generation2():
    yield from generation1()


def generation3():
    for i in generation1():
        yield i


if __name__ == '__main__':
    a = [i for i in generation2()]
    print(a)
    b = [i for i in generation3()]
    print(b)
    """
    运行记录:
    [1, 2, 3, 4, 5]
    [1, 2, 3, 4, 5]
    generation2 的语法和 generation3 的语法所发挥的作用是一样,且 geneeration2 的讲法更精简
    """

参考书籍

pyton高级编程(作者:Luke Sneeringer)

posted @ 2019-02-08 11:52  朝行  阅读(406)  评论(0编辑  收藏  举报