ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

生成器与 yield 语句

2021-06-15 22:33:45  阅读:195  来源: 互联网

标签:语句 调用 函数 生成器 yield next data


生成器

有些情况下,序列或集合内的元素的个数非常巨大,如果全制造出来并放入内存,对计算机的压力是非常大的。
如果元素可以按照某种算法推算出来,需要就计算到哪个,就可以在循环的过程中不断推算出后续的元素,而不必创建完整的元素集合,从而节省大量的空间。
在Python中,这种一边循环一边计算出元素的机制,称为生成器:generator。

生成器的优点

  • 节省内存。生成器只有在用的时候会生成,所以,当我们没有用到这个列表的时候它就不会存在,也不会占内存
  • 节省代码,减少代码量同时提高代码可读性。如下:同样的功能,但是使用生成器只用一句代码
1. 生成器推导式

2. yield 关键字

在 Python中,使用yield返回的函数会变成一个生成器(generator)。 在调用生成器的过程中,每次遇到yield时函数会暂停并保存当前所有的运行信息,返回yield的值。并在下一次执行next()方法时从当前位置继续运行。
在 满足判断条件的情况下,直接 return 了,生成器没有产生值,所以再次调用的时候引发 StopIteration 异常。

  • 在一个函数体内使用 yield 表达式会使这个函数变成一个生成器,并且在一个 async def 定义的函数体内使用 yield 表达式会让协程函数变成异步的生成器。

  • python 官方文档对于生成器调用机制的解释如下:
    当一个生成器函数被调用的时候,它返回一个迭代器,称为生成器。然后这个生成器来控制生成器函数的执行。当这个生成器的某一个方法被调用的时候,生成器函数开始执行。这时会一直执行到第一个 yield 表达式,在此执行再次被挂起,给生成器的调用者返回 expression_list 的值。挂起后,我们说所有局部状态都被保留下来,包括局部变量的当前绑定,指令指针,内部求值栈和任何异常处理的状态。通过调用生成器的某一个方法,生成器函数继续执行。此时函数的运行就和 yield 表达式只是一个外部函数调用的情况完全一致。恢复后 yield 表达式的值取决于调用的哪个方法来恢复执行。 如果用的是 next() (通常通过语言内置的 for 或是 next() 来调用) 那么结果就是 None. 否则,如果用 send(), 那么结果就是传递给send方法的值。

3. 生成器 - 迭代器的方法

在生成器已经在执行时调用以下任何方法都会引发 ValueError 异常

3.1 genetator.next()

开始一个生成器函数的执行或是从上次执行的 yield 表达式位置恢复执行。 当一个生成器函数通过 next() 方法恢复执行时,当前的 yield 表达式总是取值为 None。 随后会继续执行到下一个 yield 表达式,其 expression_list 的值会返回给 next() 的调用者。 如果生成器没有产生下一个值就退出,则将引发 StopIteration 异常。
此方法通常是隐式地调用,例如通过 for 循环或是内置的 next() 函数。
如下所示:
while循环是用来确保生成器函数永远也不会执行到函数末尾的。只要调用next()这个生成器就会生成一个值。

如果没有 while 循环,那么再第二次调用的时候就会引发一个 StopIteration 异常。

3.2 generator.send(value)

恢复执行并向生成器函数“发送”一个值。 value 参数将成为当前 yield 表达式的结果。
send() 方法会返回生成器所产生的下一个值,或者如果生成器没有产生下一个值就退出则会引发 StopIteration。
当调用 send() 来启动生成器时,它必须以 None 作为调用参数,因为这时没有可以接收值的 yield 表达式。如果传入一个真实的值来启动,则会报错:

3.3 generator.throw(type[, value[, trace])

在生成器暂停的位置引发 type 类型的异常,并返回该生成器函数所产生的下一个值。
如果生成器没有产生下一个值就退出,则将引发 StopIteration 异常。
如果生成器函数没有捕获传入的异常,或引发了另一个异常,则该异常会被传播给调用者。

3.4 generator.close()

close 方法允许任何挂起的 finally 子句执行。
如果生成器已经由于异常或正常退出则 close() 不会做任何事。
如下所示,生成器没有启动,调用 close 则不会做任何事:

如下所示,生成器已经启动,调用 close 则会执行 finally 子句。

4. 生成器的使用例子

假如:要你实现一个获取所有素数的功能,参数是一个 list,且数据都是 int 类型.
然后写出里了下面的代码

import math

def get_primes(input_list):
	 # return (element for element in input_list if is_prime(element))
	resul = []
	for i in input_list:
		if is_prime(i):
			resul.append(i)
	return resul

# 或者更好一点的方式
def get_primes(input_list):
	 return (element for element in input_list if is_prime(element))

def is_prime(n):
	if n == 1:
		return False
	for i in range(2, int(math.sqrt(n))+1):
		if n % i == 0:
			return False
	return True

但是如果这个 list 非常大,创建这个 list 就会耗费非常大的内存,这个时候你要怎么优化?这个时候就可以使用 生成器 来实现了。
前面介绍了,在一个函数里加上 yield 语句,那么这个函数就会变成生成器。代码如下:这段代码,通过 while True 的循环,创建了一个无穷序列。

def get_primes(number):
	while True:
		if is_prime(number):
			yield number
		number += 1
def solve_number_10():
	total = 0
	for next_prime in get_primes(3):
		if next_prime < 20:
			print(next_prime)
			total += next_prime
		else:
			print(total)
			return

上述代码,调用 get_primes(3) 方法,那么将从 3 开始,获取所有素数。如果素数小于 20 ,那么就进行累加。否则 return 结束程序。

再看下面这个例子:

import random

def get_data():
    """返回0到9之间的3个随机数"""
    return random.sample(range(10), 3)

def consume():
    """显示每次传入的整数列表的动态平均值"""
    running_sum = 0
    data_items_seen = 0

    while True:
        data = yield
        data_items_seen += len(data)
        running_sum += sum(data)
        print('The running average is {}'.format(running_sum / float(data_items_seen)))

def produce(consumer):
    """产生序列集合,传递给消费函数(consumer)"""
    while True:
        data = get_data()
        print('Produced {}'.format(data))
        consumer.send(data)
        yield

if __name__ == '__main__':
    consumer = consume()
    consumer.send(None)
    producer = produce(consumer)

    for _ in range(10):
        print('Producing...')
        next(producer)

代码运行过程:

  1. 首先 定义一个 consumer 生成器
  2. 通过 send 方法启动 consumer 生成器,在遇到 yield 时函数被挂起
  3. 定义一个 producer 生成器, 循环 10 次,调用 next 方法启动 producer 生成器, 运行过程如下:
  • producer 获得 3 个随机数的序列
  • consumer.send(data) 调用 consumer 生成器,从上次被挂起的地方继续执行 consumer,consumer 运行完 print 之后,因为有 while True 语句,所以会继续循环遇到了 data = yield 语句,然后 consumer 被挂起。继续执行 producer,producer 遇到了 yield 语句,然后被挂起
  • 继续执行 next(producer) 循环上面的过程

这个过程实现了一个协程,这样可以模拟一个伪并发。
协程就是你可以暂停执行的函数"。也就是yield。当一个函数在执行过程中被阻塞时,就用yield挂起,然后执行另一个函数。当阻塞结束后,可以用next()或者send()唤醒。
相比多线程,协程的好处是它在一个线程内执行,避免线程之间切换带来的额外开销,而且协程不存在加锁的步骤。

标签:语句,调用,函数,生成器,yield,next,data
来源: https://www.cnblogs.com/mayytest1202/p/12022133.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有