一.为什么要有生成器
秉着先问为什么,再问怎么做的原则,我们来看看为什么python会添加生成器这个功能。
python在数据科学领域可以说是很火。我想有一部分的功劳就是它的生成器了吧。
我们知道我们可以用列表储存数据,可是当我们的数据特别大的时候建立一个列表的储存数据就会很占内存的。这时生成器就派上用场了。它可以说是一个不怎么占计算机资源的一种方法。
二.简单的生成器
我们可以用列表推导(生成式)来初始化一个列表:
list = [x*2 for x in range(10)]
print(list) #output:[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
我们用类似的方式来生成一个生成器,只不过我们这次将上面的[ ]换成( ):
generator= (x*2 for x in range(10))
print(generator) #output: <generator object <genexpr> at 0x000002397C0A0F68>
看到上面print(generator) 并不是直接输出结果,而是告诉我们这是一个生成器和在什么位置。那么我们要怎么调用这个generator呢。
第一种:
for item in generator:
print(item)
#output:
0
2
4
6
8
第二种:
print(next(generator))#output:0
print(next(generator))#output:2
print(next(generator))#output:4
print(next(generator))#output:6
print(next(generator))#output:8
print(next(generator))#output:Traceback (most recent call last):StopIteration
第三种:
print(generator.__next__())#output:0
print(generator.__next__())#output:2
print(generator.__next__())#output:4
print(generator.__next__())#output:6
print(generator.__next__())#output:8
print(generator.__next__())#output:Traceback (most recent call last):StopIteration
怎么理解生成器的原理?
from collections import Iterable, Iterator
generator= (x*2 for x in range(5))
print(generator)
print(isinstance(generator,Iterator)) #判断是不是迭代器 #output:True
print(isinstance(generator,Iterable)) #判断是不是可以迭代 #output:True
用法详解
凡是可以for循环的,都是Iterable
凡是可以next()的,都是Iterator
集合数据类型如list,truple,dict,str,都是Itrable不是Iterator,但可以通过iter()函数获得一个Iterator对象
Python中的for循环就是通过next实现的:
for x in [1,2,3,4,5]:
pass
等价于
#先获取iterator对象
it = iter([1,2,3,4,5])
while True:
try:
#获取下一个值
x = next(it);
except StopIteration:
# 遇到StopIteration就退出循环
break
生成器本身就是一个迭代器
我们在内部封装好了算法,并规定好在某个条件下就返回一个结果给调用者。(x*2 for x in range(5))就是这样子实现的,并不是实现了(0,2,3,6,8)然后在一个个迭代出来,而是逐个生成。这就是为什么next(generator)可以作用了。
三、应用
1、开头说到生成器生成大量数据的时候可帮助系统节省内存。真的是这样吗?通过下面的代码感受下:
import time
def get_time(func): #获取一段代码的运行时间
def wraper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print("Spend:", end_time - start_time)
return result
return wraper
@get_time
def _list(n):
l1 = [list(range(n)) for i in range(n)]
del l1
@get_time
def _generator(n):
ge = (tuple(range(n)) for i in range(n))
del ge
_list(1000) #output:Spend: 0.029321908950805664
_generator(1000) #output:Spend: 0.0
列表是将0-999都生成后放进一个列表里面了,所以用得时间比较多。而生成器只是封装了算法,每次调用在去调用算法,这样做就可以做到节省内存了。
2、yield 关键词
前面只告诉我们用( )来创建一个生成器。如果我们想定义一个自己的生成器函数怎么办?用return好像不行。没关系,python有yield的关键词。其作用和return的功能差不多,就是返回一个值给调用者,只不过有yield的函数返回值后函数依然保持调用yield时的状态,当下次调用的时候,在原先的基础上继续执行代码,直到遇到下一个yield或者满足结束条件结束函数为止。
def test():
yield 1
yield 2
yield 3
t = test()
print(next(t))#output:1
print(next(t))#output:1
print(next(t))#output:1
print(next(t))#output:Traceback (most recent call last):StopIteration
将 “杨辉三角” 的算法封装成生成器,需要的时候去生成,这样就不会占用大量的电脑内存资源。
def triangle():
_list, new_list = [], []
while True:
length = len(_list)
if length == 0:
new_list.append(1)
else:
for times in range(length + 1):
if times == 0:
new_list.append(1)
elif times == length:
new_list.append(1)
else:
temp = _list[times - 1] + _list[times]
new_list.append(temp)
yield new_list #返回值,然后挂起函数,等待下一次调用
_list = new_list.copy()#调用后会继续执行下去
new_list.clear()
n = 0
for result in triangle():
n += 1
print(result)
if n == 7:
break