通过列表生成式,我们可以直接创建一个列表,但是,受到内存限制,列表容量肯定是有限的,而且创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间,在Python中,这种一边循环一边计算的机制,称为生成器:generator
生成器是一个特殊的程序,可以被用作控制循环的迭代行为,python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器。
生成器类似于返回值为数组的一个函数,这个函数可以接受参数,可以被调用,但是,不同于一般的函数会一次性返回包括了所有数值的数组,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,而且允许调用函数可以很快的处理前几个返回值,因此生成器看起来像是一个函数,但是表现得却像是迭代器
python中的生成器
要创建一个generator,有很多种方法,第一种方法很简单,只有把一个列表生成式的[]中括号改为()小括号,就创建一个generator
举例如下:
#列表生成式
lis=[x*xforxinrange(10)]
print(lis)
#生成器
generator_ex=(x*xforxinrange(10))
print(generator_ex)
结果:
[0,1,4,9,16,25,36,49,64,81]
at0x000002A4CBF9EBA0>
那么创建list和generator_ex,的区别是什么呢?从表面看就是[]和(),但是结果却不一样,一个打印出来是列表(因为是列表生成式),而第二个打印出来却是at0x000002A4CBF9EBA0>,那么如何打印出来generator_ex的每一个元素呢?
如果要一个个打印出来,可以通过next()函数获得generator的下一个返回值:
#生成器
generator_ex=(x*xforxinrange(10))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
结果:
Traceback(mostrecentcalllast):
File"列表生成式.py",line42,in
print(next(generator_ex))
StopIteration
大家可以看到,generator保存的是算法,每次调用next(generaotr_ex)就计算出他的下一个元素的值,直到计算出最后一个元素,没有更多的元素时,抛出StopIteration的错误,而且上面这样不断调用是一个不好的习惯,正确的方法是使用for循环,因为generator也是可迭代对象:
#生成器
generator_ex=(x*xforxinrange(10))
foriingenerator_ex:
print(i)
结果:
所以我们创建一个generator后,基本上永远不会调用next(),而是通过for循环来迭代,并且不需要关心StopIteration的错误,generator非常强大,如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
比如著名的斐波那契数列,除第一个和第二个数外,任何一个数都可以由前两个相加得到:
1,1,2,3,5,8,12,21,34.....
斐波那契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
#fibonacci数列
deffib(max):
n,a,b=0,0,1
whilen a,b=b,a+b n=n+1 print(a) return'done' a=fib(10) print(fib(10)) a,b=b,a+b其实相当于t=a+b,a=b,b=t,所以不必写显示写出临时变量t,就可以输出斐波那契数列的前N个数字。上面输出的结果如下: 仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。 也就是说上面的函数也可以用generator来实现,上面我们发现,print(b)每次函数运行都要打印,占内存,所以为了不占内存,我们也可以使用生成器,这里叫yield。如下: deffib(max): n,a,b=0,0,1 whilen yieldb a,b=b,a+b n=n+1 return'done' a=fib(10) print(fib(10)) 但是返回的不再是一个值,而是一个生成器,和上面的例子一样,大家可以看一下结果: 那么这样就不占内存了,这里说一下generator和函数的执行流程,函数是顺序执行的,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次被next()调用时候从上次的返回yield语句处急需执行,也就是用多少,取多少,不占内存。 deffib(max): n,a,b=0,0,1 whilen yieldb a,b=b,a+b n=n+1 return'done' a=fib(10) print(fib(10)) print(a.__next__()) print(a.__next__()) print(a.__next__()) print("可以顺便干其他事情") print(a.__next__()) print(a.__next__()) 结果: 可以顺便干其他事情 在上面fib的例子,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代: deffib(max): n,a,b=0,0,1 whilen yieldb a,b=b,a+b n=n+1 return'done' foriinfib(6): print(i) 结果: 但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果拿不到返回值,那么就会报错,所以为了不让报错,就要进行异常处理,拿到返回值,如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中: deffib(max): n,a,b=0,0,1 whilen yieldb a,b=b,a+b n=n+1 return'done' g=fib(6) whileTrue: try: x=next(g) print('generator:',x) exceptStopIterationase: print("生成器返回值:",e.value) break 结果: generator:1 generator:1 generator:2 generator:3 generator:5 generator:8 生成器返回值:done 还可以通过yield实现在单线程的情况下实现并发运算的效果 由上面的例子我么可以发现,python提供了两种基本的方式 生成器函数:也是用def定义的,利用关键字yield一次性返回一个结果,阻塞,重新开始 生成器表达式:返回一个对象,这个对象只有在需要的时候才产生结果 以上内容为大家介绍了什么是python生成器?,希望对大家有所帮助,如果想要了解更多Python相关知识,请关注IT培训机构:千锋教育。