首先在了解解析式之前,我们先来看一个列子:一个列表,元素是0-9,列表中的每个值自增1,该如何实现:
方法一:遍历列表,对其元素进行加1操作后放到一个新的列表中
1 lst = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 2 3 for index, i in enumerate(lst): 4 lst[index] += 1 5 print(lst)
方法二:通过map函数来实现
a = map(lambda x:x+1, lst) print(a) for x in a: print(x)
方法三:通过列表解析式,一行搞定
1 lst2 = [x+1 for x in lst] 2 print(lst2)
方法三就是列表解析式的写法,返回一个新的列表。
那么什么是生成器呢?通过列表解析式我们可以发现,它会直接创建一个新的列表,这样不好的地方就是占用内存,我们知道内存是有限的,如果列表中的元素有几百万,而有时候就仅仅需要个别的数据,那么就会大大浪费内存的空间。
所以,我们是否可以想一种办法来解决这个问题呢?在Python中,生成器就很好的解决了这个问题。我们可以假设列表中的元素能否通过某种算法来推算出来呢?在需要某个数据的时候通过计算来得到这个数据,这样就不会直接生成一个列表来存储许多无用的数据了。在python中,这种一边循环一边计算的机制,就是生成器。
生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,使用next()函数和send()函数可以恢复生成器继续下次计算。生成器不同于一般的函数会一次性返回所有数据,生成器一次只能产生一个数据,这样就不会占用大量的内存空间。
那么如何去创建一个生成器,这种方式比较简单,就是将列表解析式的[]换成(),就可以创建一个生成器(generator):
1 generator_demo = (x for x in range(6)) 2 print(generator_demo) 3 4 运行结果: 5at 0x0000000000C3E360>
从结果可以看出,生成器返回的是一个generator对象。而列表解析式返回一个列表。那么如何去将生成器中的元素一个一个打印出来呢?这就需要next()函数来进行操作:
1 generator_demo = (x for x in range(3)) 2 3 print(next(generator_demo)) 4 print(next(generator_demo)) 5 print(next(generator_demo)) 6 print(next(generator_demo)) 7 8 运行结果: 9 0 10 1 11 2 12 Traceback (most recent call last): 13 File "C:/Users/wj/PycharmProjects/MxOnline/test.py", line 10, in14 print(next(generator_demo)) 15 StopIteration
可以看出,生成器保存的是算法,通过next可以一次一次计算它的元素,直到最后一个元素的时候继续next会抛出StopIteration的错误。在生产环境中,是基本不会用next这个方法的。因为生成器也是可迭代对象,可以通过for循环去迭代它的值:
1 generator_demo = (x for x in range(3)) 2 3 for x in generator_demo: 4 print(x)
通过这种方法来迭代它的值就不会抛出StopIteration的错误了。
这种创建生成器的写法固然简单,但是如果是一个算法逻辑比较复杂的时候,就不适合通过这种简单的写法来创建生成器了,我们可以通过函数的形式来创建生成器,例如著名的斐波那契数列:
1 def fib(max): 2 n, a, b = 0, 0, 1 3 while n < max: 4 a, b = b, a+b 5 print(a) 6 n += 1 7 8 return 'done'
可以看出,fib函数实际上就是定义了斐波那契数列的运算规则,从第一个元素开始,去推算出后面的元素,这种逻辑就非常想生成器。那么我们就可以将这个函数去定义成一个生成器函数,通过yield关键字来定义:
1 def fib(max): 2 n, a, b = 0, 0, 1 3 while n < max: 4 yield b 5 a, b = b, a+b 6 n += 1 7 8 print(fib(10)) 9 10 运行结果: 11
从运行结果可以看出,返回了一个generator对象,这就说明将这个函数定义成了一个生成器函数。这里说一下普通函数和生成器函数的执行流程,普通函数是顺序执行的,遇到return关键字就返回函数的值退出函数。而生成器函数是每次调用next的时候执行,遇到yield关键字返回第一个元素,然后将函数挂起,等待下一次唤醒继续计算后面的值,也就是取多少用多少,不会去占用内存空间。
这个生成器函数也是可迭代对象,所以可以通过for循环去迭代他的值,而不用去通过next去取值。
我们还可以通过yield来实现在单线程模式下实现并发运算的效果:
1 import time 2 3 def consumer(name): 4 print('{}老师说:准备上课了'.format(name)) 5 while True: 6 lesson = yield 7 print('开始{}了,{}老手来讲课了!'.format(lesson, name)) 8 9 10 def producer(): 11 name1 = consumer('张老手') 12 name2 = consumer('王老手') 13 name1.__next__() 14 name2.__next__() 15 16 for x in range(4): 17 time.sleep(1) 18 print('到了两名同学') 19 name1.send(x) 20 name2.send(x)
从这个例子的运行结果可以看出,next和send的区别,next是唤醒函数,返回一次值,send是唤醒函数,并向生成器内部yield传递一个值,并改变yield的返回值。
那么什么是迭代器呢?迭代就是循环,是指在正确的范围内返回期待的数据以及在超出范围以后抛出StopIteration的错误停止迭代。
可以直接作用于for循环的数据类型有,字符串、列表、元组、字典、集合等,还有生成器也可以进行迭代,这些直接作用于for循环的对象都可以叫做可迭代对象Iterable。
一个实现了iter方法的对象是可迭代的,一个实现了next方法并且是可迭代的对象是迭代器。