您的位置:首页 > 编程语言 > Python开发

深入浅出学习Python的yield和generator

2018-03-21 19:19 736 查看

背景

之前走马观花接触过Python协程的概念,这两天和一个同事聊到了协程,死活想不起来曾经看过的东西,就记得一个yield,概念不清;

所以想捋一捋相关的东西,此篇作为学习的记录。

Generator

generator(生成器)保存的是算法,可以理解为一个特殊的函数,有迭代(
可迭代的对象都有一个__next()__成员方法
)的属性

可以被用作控制循环的迭代行为,做到一边循环一边计算;特点是只有被调用的的时候才会生成,能做到不多占用系统的资源。

在我们日常工作过程中接触最多的generator可能就是Python3.X中的range函数,我们来看一下它和Python2.x中range的用法区别:

# python2
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> type(range(10))
<type 'list'>

# python3
>>> range(10)
range(0, 10)
>>> type(range(10))
<class 'range'>

>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


如示例,Python2 是直接生成的列表list,而Python3中调用range(10)实际上是生成了一个range类,需要转换才能生成list。

Python2这样直接生成列表的机制,在实际使用中容量可能会受到内存的限制。而且如果创建一个包含100万个元素的列表,而我们又仅仅只需要访问前面几个元素,那绝大多数的内存占用就会白白浪费。所以Python3的开发者们才会在这一个小小的函数上下这么大的功夫。

Generator的使用

创建generator:列表生成式

创建generator方法有很多,最直接最简单的方法就是使用 列表生成式

>>> L = [x * 2 for x in range(5)]
>>> L
[0, 2, 4, 6, 8]
>>> G = (x * 2 for x in range(5))
>>> G
<generator object <genexpr> at 0x000000000309DD58>


如上例,只要把一个列表生成式的
[]
改成
()
,就创建了一个generator

我们可以通过
next()函数获得generator的返回值


>>> next(G)
0
>>> next(G)
2
>>> next(G)
4
>>> next(G)
6
>>> next(G)
8
>>> next(G)
Traceback (most recent call last):
File "<pyshell#16>", line 1, in <module>
next(G)
StopIteration


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

当然,这个只是测试,正确的使用方式是使用for循环,
在for循环中,会自动遵循迭代规则
,每次调用next()函数,而且不需要关心StopIteration的错误。

>>> for i in G:
print(i)
0
2
4
6
8


创建generator: 直接定义生成器函数

# 普通函数
def commom_func(max):
print("create counter")
counter = 0
while counter < max:
print(counter)
print('counter increase')
counter += 1

# 生成器函数
def yield_func(max):
print("create counter")
counter = 0
while counter < max:
yield counter
print('counter increase')
counter += 1

# 生成器函数调用
if __name__ == '__main__':
num = yield_func(5)
print(next(num))
print(next(num))
print(next(num))


---
# 生成器函数调用输出
create counter
0
counter increase
1
counter increase
2


从上面这个例子可以看出以下几点:

在yield_func函数中出现了关键字yield,这个函数返回一个生成器(通过第一行输出可以看出来),用来产生连续的n值,生成器每次只产生一个结果值

在创造生成器实例的时候,只需要像普通函数一样调用就可以,但是这个调用却不会执行这个函数

next()函数将生成器对象作为自己的参数
,在第一次调用的时候,他执行了yield_func函数到yield语句,返回产生的值0

我们重复的调用next()函数,每次他都会从上次被挂起的地方开始执行,直到再次遇到了yield关键字

如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator


下面是一个更直观的例子

def step_test():
print('step 1')
yield 1
print('step 2')
yield 2
print('step 3')
yield 3


调用该generator时,
首先要生成一个generator对象
,然后用next()函数不断获得下一个返回值:

>>> test = step_test()
>>> next(test)
step 1
1
>>> next(test)
step 2
2
>>> next(test)
step 3
3


总结

generator是非常强大的工具,在Python中,可以简单地把列表生成式改成generator

对于函数改成的generator来说,遇到return语句或者执行到函数体最后一行语句,就是结束generator的指令,for循环随之结束。

普通函数调用直接返回结果,generator函数的“调用”实际返回一个generator对象:

>>> step_test()
<generator object step_test at 0x0000000001DE5B48>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: