标签:元组 top 简单 线程 mil 值传递 存储空间 输出 span
1、生成器概念
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]
改成()
,就创建了一个generator:
1 >>> b = (i**2 for i in range(10)) 2 >>> b 3 <generator object <genexpr> at 0x0000022E4EFD8780> 4 >>> for n in b: #用for循环打印所有元素 5 print(n) 6 0 7 1 8 4 9 9 10 16 11 25 12 36 13 49 14 64 15 81
如果要一个一个打印出来,可以通过__next__()
函数获得generator的下一个返回值:
1 >>> n = (i**2 for i in range(10)) 2 >>> print(n.__next__()) 3 0 4 >>> print(n.__next__()) 5 1 6 >>> print(n.__next__()) 7 4 8 >>> print(n.__next__()) 9 9 10 >>> print(n.__next__()) 11 16 12 >>> print(n.__next__()) 13 25 14 >>> print(n.__next__()) 15 36 16 >>> print(n.__next__()) 17 49 18 >>> print(n.__next__()) 19 64 20 >>> print(n.__next__()) 21 81 22 >>> print(n.__next__()) #当没有更多元素时,会抛出异常 23 Traceback (most recent call last): 24 File "<pyshell#34>", line 1, in <module> 25 print(n.__next__()) 26 StopIteration
2、生成器的其它姿势
generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for
循环无法实现的时候,还可以用函数来实现。
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
1 def fib(num): 2 n, a, b = 0, 0, 1 3 while n < num: 4 print(b) 5 a, b = b, a+b 6 n += 1 7 return "done" # 异常的时候打印该消息 8 ‘‘‘ 9 注意赋值语句 a, b = b, a+b,相当于如下 10 tmp = (b, a+b) #tmp为一个元组 11 a = tmp[0] 12 b = tmp[1] 13 ‘‘‘
然后执行fib()便可以打印出前N个数。仔细观察,可以看出,fib
函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。
也就是说,上面的函数和generator仅一步之遥。要把fib
函数变成generator,只需要把print(b)
改为yield b
就可以了:
1 def fib(num): 2 n, a, b = 0, 0, 1 3 while n < num: 4 yield b # 将函数变为生成器 5 a, b = b, a+b 6 n += 1 7 return "done" # 异常的时候打印该消息
这就是定义generator的另一种方法。如果一个函数定义中包含yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator:
1 >>> num = fib(10) 2 >>> num 3 <generator object fib at 0x0000022E4EFD88E0> 4 >>> print(num.__next__()) 5 1 6 >>> print(num.__next__()) 7 1 8 >>> print(num.__next__()) 9 2 10 >>> print(num.__next__()) 11 3 12 >>> print(num.__next__()) 13 5 14 >>> print(num.__next__()) 15 8 16 >>> print(num.__next__()) 17 13 18 >>> print(num.__next__()) 19 21 20 >>> print(num.__next__()) 21 34 22 >>> print(num.__next__()) 23 55 24 >>> print(num.__next__()) 25 Traceback (most recent call last): 26 File "<pyshell#65>", line 1, in <module> 27 print(num.__next__()) 28 StopIteration: done
这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return
语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。
f = fib(10)
print(f)
print(f.__next__())
print(f.__next__())
print(f.__next__())
print("从这里开始,每一个数都由前面紧邻的两个数相加得到。")
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
#以下为输出结果
1
1
2
从这里开始,每一个数都由前面紧邻的两个数相加得到。
3
5
8
13
21
34
55
在上面fib
的例子,我们在循环过程中不断调用yield
,就会不断中断(视频11节2分20秒)。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。
同样的,把函数改成generator后,我们基本上从来不会用next()
来获取下一个返回值,而是直接使用for
循环来迭代:
1 >>> num = fib(10) 2 >>> for n in num: 3 print(n) 4 1 5 1 6 2 7 3 8 5 9 8 10 13 11 21 12 34 13 55
但是用for
循环调用generator时,发现拿不到generator的return
语句的返回值。如果想要拿到返回值,必须捕获StopIteration
错误,返回值包含在StopIteration
的value
中:
1 >>> g = fib(6) 2 >>> while True: 3 try: 4 x = next(g) 5 print("g:", x) 6 except StopIteration as e: # 该错误表示无法继续返回下一个值了 7 print("Generator return value:", e.value) 8 break 9 #以下为输出结果 10 g: 1 11 g: 1 12 g: 2 13 g: 3 14 g: 5 15 g: 8 16 Generator return value: done
还可以通过yield实现单线程下的并行计算效果:
1 import time 2 3 4 def customer(name): 5 print("%s准备吃包子啦!" % name) 6 while True: 7 baozi = yield # yield的作用保存当前状态并返回 8 print("包子[%s]来了!被[%s]吃了!" % (baozi, name)) 9 10 11 def producer(name): 12 c1 = customer("druid") 13 c2 = customer("alex") 14 c1.__next__() # next只 15 c2.__next__() 16 print("老子准备开始吃包子了!") 17 for i in range(10): # 做十个包子 18 time.sleep(1) 19 print("做了一个包子,一人一半。") 20 c1.send(i) # 继续回到生成器,并且将值传递给yield 21 c2.send(i) 22 23 24 producer("druid") 25 26 #以下为输出结果 27 druid准备吃包子啦! 28 alex准备吃包子啦! 29 老子准备开始吃包子了! 30 做了一个包子,一人一半。 31 包子[0]来了!被[druid]吃了! 32 包子[0]来了!被[alex]吃了! 33 做了一个包子,一人一半。 34 包子[1]来了!被[druid]吃了! 35 包子[1]来了!被[alex]吃了! 36 做了一个包子,一人一半。 37 包子[2]来了!被[druid]吃了! 38 包子[2]来了!被[alex]吃了! 39 做了一个包子,一人一半。 40 包子[3]来了!被[druid]吃了! 41 包子[3]来了!被[alex]吃了! 42 做了一个包子,一人一半。 43 包子[4]来了!被[druid]吃了! 44 包子[4]来了!被[alex]吃了! 45 做了一个包子,一人一半。 46 包子[5]来了!被[druid]吃了! 47 包子[5]来了!被[alex]吃了! 48 做了一个包子,一人一半。 49 包子[6]来了!被[druid]吃了! 50 包子[6]来了!被[alex]吃了! 51 做了一个包子,一人一半。 52 包子[7]来了!被[druid]吃了! 53 包子[7]来了!被[alex]吃了! 54 做了一个包子,一人一半。 55 包子[8]来了!被[druid]吃了! 56 包子[8]来了!被[alex]吃了! 57 做了一个包子,一人一半。 58 包子[9]来了!被[druid]吃了! 59 包子[9]来了!被[alex]吃了!
标签:元组 top 简单 线程 mil 值传递 存储空间 输出 span
原文地址:http://www.cnblogs.com/Druidchen/p/7911246.html