语言基础之:生成器Generator

在 Python 中,使用了 yield 的函数被称为生成器(generator)。

  • 只有一个I.next()方法
    跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。

  • 只记录当前位置:
    在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。

  • 调用一个生成器函数,返回的是一个迭代器对象。

1 列表生成式

列表生成式在调用列表之前创建完整的列表,如果列表参数庞大,会占用大量的内存空间。

>>> L = [x*2 for x in range(10)]
>>> L
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
>>> L[3]
6
>>> len(L)
10
>>>

2 生成器

generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

>>> I = (i*2 for i in range(10000))
>>> print(I)
<generator object <genexpr> at 0x0000021DB16747B0>
# 创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。
>>> for value in I:
...     print(value)
0
...
11060
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
KeyboardInterrupt
>>> I[100]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'generator' object is not subscriptable
>>> I.__next__()
11062
>>> I.__next__()
11064
>>> I.__next__()
11066
>>> I.__next__()
11068
>>> I.__next__()
11070
>>>

2.1 斐波拉契数列(Fibonacci)

# Description:打印斐波拉契数列(Fibonacci):
# 除第一个和第二个数外,任意一个数都可由前两个数相加得到:
# 1, 1, 2, 3, 5, 8, 13, 21, 34, ...


# 斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
def fib(max):
    x, y = 0, 1
    for i in range(max):
        print(y)
        x, y = y, x + y


max = int(input('max:'))
fib(max)

注意,赋值语句:

x, y = y, x + y

相当于:

z = (y, x + y) # z是一个tuple
x = z[0]
y = z[1]
# 结论:不必显式写出临时变量z就可以赋值。

2.2 yield

  • 如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator
def fib(max):
    x, y = 0, 1
    for i in range(max):
        # print(y)
        yield y
        x, y = y, x + y
    # return 'done'


f = fib(10)
print(f)
# <generator object fib at 0x000001E79A57CAC0>
print(f.__next__())
print(f.__next__())
print('-----out-----')
print(f.__next__())
print(f.__next__())
print('-----start loop-----')
for i in f:
    print(i)

# 输出:
1
1
-----out-----
2
3
-----start loop-----
5
8
13
21
34
55

2.2.1 yield解析

def Foo():
    print('starting eating baozi...')
    while True:
        baozi = yield 'return_code'
        print('\033[34;1m c\033[0m is eating baozi %s'% baozi)


if __name__ == '__main__':

    f = Foo()
    # print(next(f))
    '''
    输出结果:
    starting eating baozi...
    None
    '''

    # 单步输出
    print('step 1 :', f.__next__())
    print('*'*25)
    print('step 2 :', f.__next__())
    print('*'*25)
    print('step 3 :', f.send(3))
    '''
    输出结果:
    starting eating baozi...
    step 1 : return_code
    *************************
     c is eating baozi None
    step 2 : return_code
    *************************
     c is eating baozi 3
    step 3 : return_code
    '''

  1. 程序执行后,会得到一个生成器f,f = {generator} <generator object Foo at 0x0000028EFD744DD0>
  2. 调用f__next__()指针下移一位,执行Foo函数,打印’starting eating baozi…’;再进入while循环,执行’yield’语句,会暂停Foo函数执行、返回’return_code’并跳出此函数,此时由于程序已经跳出此函数,变量’baozi’并没有被赋值。
  3. 执行print(’*’*25)
  4. 调用f.__next__()指针下移一位,Foo函数将从上个暂停的指针位置执行函数,'baozi’没有被赋值,返回None;由于while循环,执行’yield’语句,会暂停Foo函数执行、返回’return_code’并跳出此函数
  5. 执行print(’*’*25)
  6. 调用f.send(3),由于send()方法的作用是恢复generator并发送一个值给当前yield表达式。Foo函数将从上个暂停的指针位置执行函数,send()将3赋值给’baozi’变量,返回’3’,由于while循环,再执行’yield’语句,会暂停Foo函数执行、返回’return_code’并跳出此函数

2.2.2 抓取生成器返回值

def fib(max):
    x, y = 0, 1
    for i in range(max):
        # print(y)
        yield y
        x, y = y, x + y
    return 'done'
g = fib(5)
while True:
    try:
        z = next(g)
        print('z:', z)
    except StopIteration as e:
        print('Generator return value:', e.value)
        break
# 输出
z: 1
z: 1
z: 2
z: 3
z: 5
Generator return value: done

2.2.3 通过yield实现在单线程的情况下实现并发运算的效果

def consumer(name):
    print("%s 准备吃包子啦!" %name)
    while True:
       baozi = yield

       print("包子[%s]来了,被[%s]吃了!" %(baozi,name))

b1 = 'big baozi'
c = consumer('abc')
c.__next__()
c.__next__()
c.send(b1)
c.__next__()
# 输出
abc 准备吃包子啦!
包子[None]来了,[abc]吃了!
包子[big baozi]来了,[abc]吃了!
包子[None]来了,[abc]吃了!

结论:

  • yield : 保存当前状态,返回
  • next()调用yield但不会给yield传值
  • send()调用yield会给yield传值
posted @ 2020-11-04 10:11  f_carey  阅读(10)  评论(0编辑  收藏  举报  来源