news 2026/10/23 5:51:00

【python】Python: yield的用法(产生无限个素数的方式, 大批量读取数据的一种思路)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【python】Python: yield的用法(产生无限个素数的方式, 大批量读取数据的一种思路)

yield的用法(产生无限个素数的方式)

首先LZ先问下哈,有多少小伙伴了解python当中的“generators”和关键字“yield”呢?

1. 什么是Python生成器(书本定义)

Python生成器是通过调用yield返回生成器迭代器(只是我们可以迭代的对象)的函数。 可以用一个值调用yield,在这种情况下,该值被视为“生成的”值。 下次在生成器迭代器上调用next()时(例如,在for循环的下一步中),生成器从其调用yield的位置恢复执行,而不是从函数的开头恢复执行。 像局部变量的值一样,所有状态都将恢复,并且生成器将继续执行,直到下一次调用yield为止。
PS:好像还不是很理解的样子,不要紧,我们慢慢来理解一下

2. 一个小例子(热身环节)

importnumpyasnp list_test=[1,2,3,4]list_new=[x*xforxinlist_test]#对于列表来说我们可以看到使用in来进行迭代print("list_new: ",list_new)
list_new: [1, 4, 9, 16]
#注意[]变成(),这样我们my_gen得到的并不是一个列表,而是一个生成器对象,从输出中可以看出my_gen=(x*xforxinlist_test)print("my_gen: ",my_gen)
my_gen: <generator object <genexpr> at 0x7f7eca184750>
#那么生成器对象要怎么使用呢?这里通过for循环来不停调用生成器,使用next来获得对应的值#先简单有个印象,我们又使用了一个关键字,next!foriinrange(len(list_test)):print("my_gen: ",next(my_gen))
my_gen: 1 my_gen: 4 my_gen: 9 my_gen: 16
#定义一个简单的生成器函数,怎么看出来是生成器函数的呢?#可以看到函数中含有关键字yield!!!!点题,本文的重点关键字来了defg():print('1')x=yield'hello'print('2','x= ',x)y=5+(yieldx)print('3','y= ',y)# 调用生成器函数,得到一个生成器对象f=g()print(f)
<generator object g at 0x7f7eca1624f8>
#使用next()得到返回的yield的值next(f)
1 'hello'
# 出现第三个重点关键字,send,这里是强制设定 x = 5f.send(5)
2 x= 5 5
#再一次设定x =2,这时候的输出是生成器函数里的输出,但是并没有yield进行返回,#所以到达了生成器对象的最后,所以也就暂停了继续迭代!f.send(2)
3 y= 7 --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-16-37a752bbe65b> in <module> ----> 1 f.send(2) StopIteration:

3. 产生无限个素数的方法(使用yield)

#通常我们写的函数都叫做函数,一般都会有个关键字return,在调用一个函数时,#函数中定义的都是局部变量,在函数结束时,这些局部变量也就没有了,再次调用对应的函数,#又会从函数的第一行开始运行# 得到一个输入列表,里面包含有限长的数字,我们需要判断这些数字是否是素数,把素数组成一个# 新的列表,进行返回defget_primes(input_list):result_list=list()forelementininput_list:ifis_prime(element):result_list.append(element)returnresult_list#素数的定义是,一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做素数defis_prime(number):ifnumber>1:ifnumber==2:returnTrueifnumber%2==0:returnFalseforcurrentinrange(3,int(np.sqrt(number)+1),2):ifnumber%current==0:returnFalsereturnTruereturnFalse
#调用这个函数进行一个小小的实验,发现完全可以,那么还要做什么呢?get_primes([xforxinrange(10)])
[2, 3, 5, 7]

我们只是对有限个数字列表进行了素数的判断,但是当我们需要10000个,或者更多时呢,那你怎么能进行确定呢?这个是一个问题,其次,就算你能够写一个函数满足这个要求,内存为了存储这么多的数据也要消耗很大的内存,这是即使算法上可行,在内存消耗上也是不能忽视的一个问题。

#这里我们假设存在一个函数,叫做magical_infinite_range,它的功能是能够指定开始位置,#并一直到无穷个数,那这样我们就真的解决这个问题了吗?defget_primes(start):forelementinmagical_infinite_range(start):ifis_prime(element):returnelement# 我们来定一个函数,这个函数是为了解决:https://projecteuler.net/problem=10defsolve_number_10():# She *is* working on Project Euler #10, I knew it!total=2fornext_primeinget_primes(3):ifnext_prime<2000000:total+=next_primeelse:print(total)return

上述这个函数我们来仔细分析一下,好像并不能解决对应的问题,在运行到solve_number_10这个函数时,当我们运行get_primes(3)的时候,函数通过return只返回一个3就结束了,并没有解决我们想要获得无限个素数的问题,那么到底要怎么解决呢?
当然是使用生成器啊,这样既解决了无限个数的需求,也解决了整批数据返回占用大量内存的问题!

#这里我们再举一个小例子,可以使用for loop来获得对应的数据defsimple_generator_function():yield1yield2yield3forvalueinsimple_generator_function():print(value)
1 2 3
our_generator=simple_generator_function()next(our_generator)
1
our_generator.__next__()
2
next(our_generator)
3
our_generator=simple_generator_function()forvalueinour_generator:print(value)
1 2 3
# 如果超出了边界,就会暂停,这个也很正常,你只给了五个数字,非得问生成器第六个数字是什么?# 这个确实不合理吧print(next(our_generator))
--------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-34-53f126310cb8> in <module> ----> 1 print(next(our_generator)) StopIteration:
# however, we can always create a new generator# by calling the generator function again...#当然你也可以重新定义一个新的生成器,那么生成器又指向初始位置,这样也就没有问题了new_generator=simple_generator_function()print(next(new_generator))# perfectly valid
1
defget_primes(number):whileTrue:ifis_prime(number):yieldnumber number+=1
defsolve_number_10():# She *is* working on Project Euler #10, I knew it!total=2fornext_primeinget_primes(3):ifnext_prime<2000000:total+=next_primeelse:print(total)return
#运行一下我们使用生成器构建的函数,发现问题解决了,那么我们是怎么解决的呢?solve_number_10()
142913828922

我们需要先理一下代码逻辑,这样才能更好的理解生成器的工作原理:
首先运行solve_number_10(),我们进入对应的函数,到第4行的位置,进入get_primes(3)这个函数,while(True)始终为真后进入循环,3判断结果是素数,所以返回3,我们返回到solver_number_10()函数的for循环内,total的数值为2+3=5,然后再次运行get_primes(3).

小黑板竖起来,要划重点了!!再次进入get_primes(3)的时候,并不是从头开始运行的,而是运行number+=1这一行,也就是说,我们得到的number=4,在while循环中进行判断,当number=5的时候,有获得一个素数,返回,这样重复循环,直到我们得到的素数大于2000000时,结束.

那么我们怎么把值传回给生成器呢?

现在,我们又遇到了一个问题:这次,我们希望最小素数大于10,然后是100,然后是1000,依此类推,即我们需要获得大于10, 100, 1000…的最小素数。

defprint_successive_primes(iterations,base=10):# like normal functions, a gnerator function# can be assigned to a variableprime_generator=get_primes(base)prime_generator.send(None)forpowerinrange(iterations):print(prime_generator.send(base**(power+1)))defget_primes(number):whileTrue:ifis_prime(number):number=yieldnumber number+=1
print_successive_primes(10)
11 101 1009 10007 100003 1000003 10000019 100000007 1000000007 10000000019

这里需要注意两点:
首先,我们打印出generator.send的结果,这是可能的,因为send都向该生成器发送了一个值,并返回了生成器产生的值(反映了yield如何在generator函数内部工作) 。

其次,注意prime_generator.send(None)那一行,当使用send来“启动”生成器时(即,执行从生成器函数的第一行到第一个yield语句的代码),必须发送None。 这是有道理的,因为按照定义,生成器尚未到达第一个yield语句,因此,如果我们发送真实值,将没有任何东西可以“接收”它。 一旦生成器启动,我们就可以像上面一样发送值。

4. 最后再举一个消费者和生产者的模型

importrandomdefget_data():"""return 3 random integers between 0 and 9"""returnrandom.sample(range(10),3)defconsume():"""Display a running average across lists of integerss sent to it"""running_sum=0data_items_seen=0whileTrue:data=yielddata_items_seen+=len(data)running_sum+=sum(data)print('The running average is {}'.format(running_sum/float(data_items_seen)))defperoduce(sonsumer):"""Produce a set of values and forwards them to the pre-trained consumer function"""whileTrue:data=get_data()print('Produced {}'.format(data))consumer.send(data)yieldif__name__=='__main__':consumer=consume()consumer.send(None)producer=peroduce(consumer)for_inrange(10):print('Producing ...')next(producer)
Producing ... Produced [8, 6, 9] The running average is 7.666666666666667 Producing ... Produced [6, 4, 5] The running average is 6.333333333333333 Producing ... Produced [5, 7, 6] The running average is 6.222222222222222 Producing ... Produced [7, 1, 8] The running average is 6.0 Producing ... Produced [4, 9, 1] The running average is 5.733333333333333 Producing ... Produced [7, 6, 0] The running average is 5.5 Producing ... Produced [4, 7, 9] The running average is 5.666666666666667 Producing ... Produced [5, 4, 0] The running average is 5.333333333333333 Producing ... Produced [2, 6, 0] The running average is 5.037037037037037 Producing ... Produced [0, 2, 6] The running average is 4.8

5. 总结:

下面有一些总结,非常重要,希望对小伙伴们有所帮助:

1.生成器的目的是为了生成一系列数值(斐波那契数列等)
2.yield其实类似与函数的return,但是生成器函数中的变量都会对应保存
3.生成器就是一种特殊的迭代器
4.像迭代器一样,我们可以使用next()从生成器中获取下一个值,或者使用send来强制传输对应的值
5.我们也可以通过for loop来隐式的调用next()获取下一个值
6.使用生成的好处是可以节省内存,并不需要一次性返回所有的值,而是在需要的时候获取对应的数据,这就让LZ联想到了其实在深度学习,我们在读取数据时,经常会需要读取海量的数据,数据量太大的情况下是没有办法一次性全部读进内存的,最后的方法就是使用这种生成器等类似的方式

6. 参考地址:

1.https://www.jeffknupp.com/blog/2013/04/07/improve-your-python-yield-and-generators-explained/#fn:prime
2.https://www.jianshu.com/p/d09778f4e055
3.https://www.jianshu.com/p/84df78d3225a

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 5:50:00

VoxelGrid体素网格:点云下采样的工程基石与实操指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/23 5:49:59

Mac移动硬盘不显示的底层原因与12分钟排查法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/23 5:48:59

GitLab SSH密钥配置全流程:生成、验证、多账号与排错指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/23 5:48:54

Windows原生部署ComfyUI+GradioAPI实现批量生图

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/23 5:48:27

嵌入式DMA与多核场景下的内存屏障实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华