生成器

引言

倘若需要输出一组0到9的平方的数据,容易想到以下实现方法:

# 方法1(for循环)
for i in range(10):
	print(i**2)

# 方法2(lamda匿名函数)
f = map((lambda x : x**2), range(10))
for i in f:
	print(i)

# 方法3(列表生成式)
f = [x**2 for x in range(10)]
for i in f:
	print(i) 

以上方法可得到0至9的平方,但若要生成0到100000的(甚至更大的数)平方,以上方法就会出现内存占用过大的问题,此外,有时我们用列表储存一个很大的数组,但使用过程中我们只用到了最前面的几个数,就出现资源浪费的问题。因此,如果列表中的元素可以由某种算法推算出来,在循环的过程中依据该算法不断推算后续的元素,那么可以节约大量的内存。

为了解决该问题,生成器(generator)的概念被提出,即一边循环一边计算的机制。换句话说,如果以更小的内存获得更大的数据,那就用生成器。

生成器

生成器的实现方法有两种,一种是将列表生成式的[]换成(),另一种是在函数中使用yield,那么在调用该函数的时候,就创建了一个生成器对象。以下是两种实现方法:

# 方法1
generate = (x**2 for x in range(10))
print(generate)
# <generator object <genexpr> at 0x0000024518D04DB0>

# 方法2
def generate(n):
	i = 0
	while i < n:
		yield i**2
		print(i**2)
		i += 1
	return 0


f = generate(10)
print(f)
# <generator object generate at 0x0000024518D04DB0>

以上两种方法分别创建了两个生成器对象,与列表储存数组不同,我们可以认为生成器储存的是算法,那么它能够顺利地推算出后续的数据吗,我们可以继续实验。如何输出数据呢?除了可以利用for循环输出结果,也可以利用生成器的next方法。如下:

generate = (x**2 for x in range(3))
# 方法1
for i in generate:
	print(i)
# 0
# 1
# 4

generate = (x**2 for x in range(3))
# 方法2
print(next(generate))
# 0
print(generate.__next__())
# 1
print(next(generate))
# 4
print(next(generate))
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# StopIteration

可以发现,生成器确实能够顺利地推算数据。值得注意的是,输出最后一个数据后,使用next方法时,会返回一个StopIteration的异常。事实上,生成器能够迭代的原因也是next方法,重复调用该方法,直到捕捉到StopIteration异常。一般情况下,获取生成器数据不会使用next方法,而是使用for循环迭代。

生成器工作原理

总的来说,生成器可以被用作控制循环的迭代行为,每次调用yield会暂停,使用next方法时恢复生成器。因此,不同于一般函数一次性返回所有数据,生成器一次只返回一个结果,是一种惰性计算。

此外,还有send方法,它同next方法一样,可以恢复生成器;但与next不同的是,它可以传入一个新的数据作为yield表达式的整体结果。

以下具体介绍yield和send方法。

yield

首先,说明yield。yield类似 return ,它会返回一个值,并且记住这个返回的位置,下次迭代或者用next(send)恢复生成器后,代码从返回位置的下一条语句继续执行。

def generate(n):
	i = 0
	while i < n:
		a = yield i**2
        print(a)
        print(i**2)
        i += 1


f = generate(3)
f.__next__()
# 0
f.__next__()
# None
# 0
# 1
f.__next__()
# None
# 1
# 4
f.__next__()
# None
# 4
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# StopIteration

第一次使用next方法时,停止在yield。yield返回0的平方,并且a没有被赋值;
第二次使用next方法时,从print(a)开始,由于a没有被赋值,因此输出None,其次print输出0的平方,i加1,最后停止在yield,返回1的平方;
第三次使用next方法时,从print(a)开始,由于a没有被赋值,因此输出None,其次print输出1的平方,i加1,最后停止在yield,返回2的平方;
第四次使用next方法时,从print(a)开始,a同样未被赋值,因此输出None,其次print输出2的平方,i加1,循环结束,返回StopIteration异常。

send方法

在介绍yield时,可以发现每次在yield处停止,但是a并没有被赋值。而前面提到的send方法可以解决这个问题,send() 和next()一样,都能让生成器继续执行(遇到yield停),但send()能传一个值,这个值作为yield表达式整体的结果。举个栗子:

def generate(n):
	i = 0
	while i < n:
		a = yield i**2
        print(a)
        print(i**2)
        i += 1


f = generate(3)
f.__next__()
# 0
f.__next__()
# None
# 0
# 1
f.send('hi')
# hi
# 1
# 4

上面利用send方法,在第三次执行生成器时,将“hi”作为yield表达式的整体结果赋值给了a,再执行后面的语句。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值