PYthon的生成器

大家还记得上次讲到的PYthon的迭代器吗,今天我们要讲一个和它相关的,生成器。

通过列表生成式,我们可以直接创建一个列表,但是,受到内存限制,列表容量肯定是有限的,而且创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。我们先来看一个简单的列表生成器对比例子:

有一个需求,列表 [0,1,2,3,4,5,6,7,8,9],要求把列表里面的每个值加1,有两种办法:

*第一种:

PYthon的生成器

info = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
b = []
# for index,i in enumerate(info):
# print(i+1)
# b.append(i+1)
# print(b)
for index,i in enumerate(info):
info[index] +=1
print(info)
#[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

*第二种:

PYthon的生成器

info = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
a = [i+1 for i in range(10)]
print(a)

可以看到第二种比第一种要简简洁很多。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间,在Python中,这种一边循环一边计算的机制,称为生成器:generator

生成器是一个特殊的程序,可以被用作控制循环的迭代行为,python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器。

生成器类似于返回值为数组的一个函数,这个函数可以接受参数,可以被调用,但是,不同于一般的函数会一次性返回包括了所有数值的数组,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,而且允许调用函数可以很快的处理前几个返回值,因此生成器看起来像是一个函数,但是表现得却像是迭代器。

1generator

要创建一个generator,有很多种方法,第一种方法很简单,把一个列表生成式的[]中括号改为()小括号,就可以创建一个generator

# 列表生成式

lis = [x * x for x in range(10)]

print(lis)

# 生成器

generator_ex = (x * x for x in range(10))

print(generator_ex)

#[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

#<generator> at 0x0375FA70>/<generator>

PYthon的生成器

此处我们仔细看一下生成结果的两行对比,创建lis和generator_ex除了表面的[]与()的区别外,生成的一个是列表,一个则是<generator> at 0x0375FA70>。如果需要打印generator_ex 的每一个值的话:/<generator>

generator_ex = (x*x for x in range(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))
#结果:
0
1
4
9
16
25
36
49
64
81
Traceback (most recent call last):
File "D:\\\untitled\venv\lib\site-packages\IPyth on\core\interactiveshell.py", line 2869, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-5-942fb3d5fc8e>", line 12, in <module>
print(next(generator_ex))
StopIteration
/<module>/<ipython-input-5-942fb3d5fc8e>
PYthon的生成器

generator保存的是算法,每次调用next(generaotr_ex)就计算出它的下一个元素的值,直到计算出最后一个元素,没有更多的元素时,抛出StopIteration的错误,而且上面这样不断调用是一个不好的习惯,正确的方法是使用for循环,因为generator也是可迭代对象:

#生成器
generator_ex = (x*x for x in range(10))
for i in generator_ex:
print(i)
PYthon的生成器

所以我们创建一个generator后,基本上永远不会调用next(),而是通过for循环来迭代,并且不需要关心StopIteration的错误,generator非常强大,如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

2生成器函数

为什么叫生成器函数?因为它随着时间的推移生成了一个数值队列。一般的函数在执行完毕之后会返回一个值然后退出,但是生成器函数会自动挂起,然后重新拾起急需执行,它会利用yield关键字关起函数,给调用者返回一个值,同时保留了当前的足够多的状态,可以使函数继续执行,生成器和迭代协议是密切相关的,可迭代的对象都有一个__next__()__成员方法,这个方法一种可返回迭代的下一项,或是引起异常结束迭代。

def fib(max):
n,a,b =0,0,1
while n < max:
yield b
a,b =b,a+b
n = n+1
return 'done'
g = fib(6)
while True:
try:
x = next(g)
print('generator: ',x)
except StopIteration as e:
print("生成器返回值:",e.value)
break
#结果
generator: 1
generator: 1
generator: 2
generator: 3
generator: 5
generator: 8
生成器返回值: done

PYthon的生成器

这里使用yield生成器使之打印结果不占内存了。generator和函数的执行流程,函数是顺序执行的,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次被next()调用时候从上次的返回yield语句处急需执行,也就是用多少,取多少,不占内存。可是若只是如此操作也有一个弊端,在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来,而要规避这个弊端的话我们可以采取for循环的方式,可是为什么最终使用的确实while呢?因为使用for循环语句,我们就无法获取generator的return语句返回的值,因此就会报错,产生上文中出现过的StopIteration错误,要获取到返回值必须捕获StopIteration错误,返回值包含在StopIteration的value中,即采用上文中语句。

下面再来看一个生成器函数:

# 函数有了yield之后,函数名+()就变成了生成器
# return在生成器中代表生成器的中止,直接报错
# next的作用是唤醒并继续执行
# send的作用是唤醒并继续执行,发送一个信息到生成器内部
def create_counter(n):
print("create_counter")
while True:
yield n
print("increment n")
n += 1
gen = create_counter(2)
print(gen)
print(next(gen))
print(next(gen))
#生成结果
<generator>
create_counter
2
increment n
3
/<generator>
PYthon的生成器


分享到:


相關文章: