发布时间:2018-02-27 16:08:04编辑:admin阅读(3664)
先来讲一个列表生成式
列表生成式:就是一个用来生成列表的特定语法形式的表达式。
基础语法格式
[exp for iter_var in iterable]
普通创建列表是这样的
a = [1,2,3]
如果想要生成0到9的列表,一个个写太麻烦了。用列表生成式,就简单多了
a = [i for i in range(10)] print(a)
执行输出
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
值还可以做计算,比如
a = [i*2 for i in range(10)] print(a)
执行输出
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
上面一句代码,相当于运行了3行代码
a = [] for i in range(10): a.append(i*2) print(a)
执行输出,同上。
在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。
第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:
a = (i*2 for i in range(5)) print(a)
执行输出
<generator object
这是一个生成器对象,它保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
通过__next__()方法,获取下一个内容,打印3个数据
a = (i*2 for i in range(5)) print(a.__next__()) print(a.__next__()) print(a.__next__())
执行输出
0
2
4
如果数据很多呢?方法是使用for循环,因为generator也是可迭代对象:
a = (i*2 for i in range(5)) for i in a: print(i)
执行输出
0
2
4
6
8
下面对比一下列表生成式和生成器,生成一百万数据,哪个比较快
#!/usr/bin/env python # coding: utf-8 __author__ = 'www.py3study.com' import time #获取函数执行时间 def exec_time(func): start_time = time.time() func() stop_time = time.time() print('the variable run time is %s' % (stop_time - start_time)) #列表生成式 def a(): list_gen = [i * 2 for i in range(10000000)] #生成器 def b(): iteration = (i * 2 for i in range(10000000)) exec_time(a) exec_time(b)
执行输出
the variable run time is 1.0684430599212646
the variable run time is 0.0
很明显,生成器要快。为什么呢?因为它不存储所有值,而列表生成器存储了所有的值。
再举个例子
a = [i*2 for i in range(1000)] b = (i*2 for i in range(1000))
输出a的第100个值
print(a[100])
执行输出 200
输出b的第100个值
print(b[100])
执行报错
TypeError: 'generator' object is not subscriptable
为什么呢?因为前面的数据还没跑,直接咔嚓跳到100,找不到了。
它不支持像列表的获取,切片等操作。
它只有一种方式,就是一个个去取。
总结:
生成器 只有在调用时才会生成相应的数据
只记录当前的位置
只有一个__next__()方法,基本不会用到它,一般通过for循环来迭代它。
generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
def fib(max): n, a, b = 0, 0, 1 while n < max: print(b) a, b = b, a + b n = n + 1 return 'done' fib(10)
执行输出
1
1
2
3
5
8
13
21
34
55
a,b表示每个数字的前2个值,n表示第几个数
a, b = b, a + b
相当于
t = (b, a + b) # t是一个tuple a = t[0] b = t[1]
比如a等于2,后一个数是前2个数的和,也就是1+2,那么b等于3
每循环一次,把a向前推1个位置。
但不必显式写出临时变量t就可以赋值。
上面的函数可以输出斐波那契数列的前N个数:
仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。
也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:
def fib(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1 print(fib(10))
执行输出
generator object fib at 0x0000029FC0F00F68
这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:
这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
f = fib(10) print(f.__next__()) print(f.__next__()) print("============") print(f.__next__()) print(f.__next__())
执行输出
1
1
============
2
3
在上面fib的例子,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。
同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:
f = fib(10) for i in f: print(i)
但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:
def fib(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1 f = fib(10) while True: try: x = next(f) print('f:', x) except StopIteration as e: print('Generator return value:', e.value) break
执行输出
f: 1
f: 1
f: 2
f: 3
f: 5
f: 8
f: 13
f: 21
f: 34
f: 55
Generator return value: None
next(f)等同于f.__next__()
yield保持了函数的中断状态。返回当前状态的时,再次执行yield时,继续执行下面的代码。
下面讲一个吃包子的例子
先看前半段
import time def consumer(name): print("%s 准备吃包子啦!" %name) while True: baozi = yield print("包子[%s]来了,被[%s]吃了!" %(baozi,name)) c = consumer("zhang") c.__next__() c.__next__()
执行输出
zhang 准备吃包子啦!(第一次next执行效果)
包子[None]来了,被[zhang]吃了!(第二次next执行效果)
因为包子还没做,还不能吃
下面做一个包子
import time def consumer(name): print("%s 准备吃包子啦!" %name) while True: baozi = yield print("包子[%s]来了,被[%s]吃了!" %(baozi,name)) c = consumer("zhang") c.__next__() b1 = "韭菜馅" c.send(b1)
执行输出
zhang 准备吃包子啦!
包子[韭菜馅]来了,被[zhang]吃了!
send ()方法,是唤醒yield,并且给yield传值。而__next__()不会传值,只是唤醒调用。
所以输出了 包子[韭菜馅]
这里看到了2个任务,一个是吃包子,一个是做包子
下面把做包子的流程规范一下,完整代码如下
#!/usr/bin/env python # coding: utf-8 __author__ = 'www.py3study.com' import time def consumer(name): print("%s 准备吃包子啦!" %name) while True: baozi = yield print("包子[%s]来了,被[%s]吃了!" %(baozi,name)) def producer(name): c = consumer('A') c2 = consumer('B') c.__next__() c2.__next__() print("%s开始准备做包子啦!" %name) for i in range(3): time.sleep(1) print("做了2个包子!") c.send(i) c2.send(i) producer("kang")
执行输出
注意下面几行代码:
c = consumer('A') c2 = consumer('B') c.__next__() c2.__next__()
为什么声明了c和c2之后,还要执行__next__()方法?
因为consumer它不是函数,它包含了yield,所以它是一个生成器。
生成器必须要用指定的方法,才能调用,执行里面的代码。比如__next__()或者send()
执行了__next__(),就是为了输出
print("%s 准备吃包子啦!" %name)
这一段话。
之后执行
print("%s开始准备做包子啦!" %name)
下面的send()执行之后,就会执行以下代码
baozi = yield print("包子[%s]来了,被[%s]吃了!" %(baozi,name))
这个例子,就是一个简单的 协程 效果。
上一篇: python 装饰器案例解析
下一篇: python 迭代器
47618
46016
36922
34490
29100
25745
24582
19728
19270
17767
5582°
6168°
5704°
5760°
6719°
5499°
5502°
6004°
5976°
7307°