大聊PYthon----生成器
再说迭代器与生成器之前,先说一说列表生成式
列表生成式
什么是列表生成式呢?
这个非常简单!
先看看普通青年版的!
>>> a [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> b = [] >>> for i in a:b.append(i+1) ... >>> b [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> a = b >>> a [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
再看看原值修改版的!
a = [1,3,4,6,7,7,8,9,11] for index,i in enumerate(a): a[index] +=1 print(a)
再看看文艺青年版的!
>>> a [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> a = map(lambda x:x+1, a) >>> a <map object at 0x101d2c630> >>> for i in a:print(i) ... 2 3 4 5 6 7 8 9 10 11
再看看装逼青年版
>>> a = [i+1 for i in range(10)] >>> a [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
这就叫做列表生成!
列表生成的主要作用是使代码更简洁
说完了列表生成式,那现在就开始说说生成器
生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。(说白了就是浪费内存)
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
生成器的机制:
a、生成器只有在调用的时候才会生成相应的数据.
先看一段代码:
>>> Good_man = [i*2 for i in range(100)] >>> Good_man [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72 , 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134 , 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198]
在路上你遇到了一个好人,他说:我能把你手里的100块钱变成200块,只要你给我,我就给你变!于是你就给他100块,很快的时间,他就把变的200块钱给你了,也没有贪图你一分钱!
再看一段代码:
>>> Bad_man = (i*2 for i in range(100)) >>> Bad_man <generator object <genexpr> at 0x000000000066BE08> # 生成器 >>>
遇到好人之后,他将100块变成了200块,于是你的心理美滋滋,想着如果再能碰到这样的人就好,当你走着走着,突然看到一个很道貌盎然的男人,这个男人在路边看到了一个好人给他变钱,于是这个男人想把你的钱骗走,于是他就跟你说,他也能把100变为200,于是你就把钱给他了,但是他没有变出来,不是他不会变,而是他不想变,你看到他没给你钱于是你想朝他要 ,你先朝他要2块钱,他也就只给你两块钱,你想要10块,他只给你10块,多一分钱都不会给你,他就是这么吝啬!他不会一起把钱全部给你,只会两块两块的给你,直到把200块钱都给你,看下面代码!
>>> Bad_man = (i*2 for i in range(100)) >>> for i in Bad_man: ... print(i) ... 0 2 4 · · · · · 194 196 198
b、只记录当前位置,只有一个__next__()方法.(在python2.7中是next( ))
Bad_man = (I*2 for i in range(1000000) for i in Bad_man: print(i) #输出 0 2 4 6 8 · · · · 147946 # 突然在这里发生了错误 Traceback (most recent call last): File "<stdin>", line 2, in <module> KeyboardInterrupt >>> ^X File "<stdin>", line 1 ^ SyntaxError: invalid syntax
于是你想查找出现错误后的下一个数,通过
>>> Bad_man.__next__() 147948
找到了错误的一下个数,但是你想能不能找到出现错误的数的前几个代码呢?于是你就查找各种资料,结果发现不能,于是经过查找,发现,只能一个个的查找后面的数值,而且只能用一种__next__()方法,想查找前面的数值是不可能的了!
用函数来实现生成器
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
仔细观察,可以看出,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 return 'done' # return的作用是 程序异常时打印出消息 print(fib(10))
这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:
输出结果:
<generator object fib at 0x00000000006DBD00>
怎么样?很眼熟吧!
这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
f = fib(10) print(f) print(f.__next__()) print(f.__next__()) print(f.__next__()) print(f.__next__()) print("下次玩的舒服点哦!") print(f.__next__()) print(f.__next__()) # 输出 <generator object fib at 0x0000000000AD5D00> 1 1 2 3 下次玩的舒服点哦! 5 8 13
在上面fib的例子,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。
同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:
f = fib(10) print(f) for n in f: print(n) # 打印 <generator object fib at 0x0000000000665D00> 1 1 2 3 5 8 13 21 34 55
但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:
g = fib(10) while True: try: x = next(g) print('g:', x) except StopIteration as e: print('Generator return value:', e.value) break # 输出 g: 1 g: 1 g: 2 g: 3 g: 5 g: 8 g: 13 g: 21 g: 34 g: 55 Generator return value: done