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 3our_generator=simple_generator_function()next(our_generator)1our_generator.__next__()2next(our_generator)3our_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 valid1defget_primes(number):whileTrue:ifis_prime(number):yieldnumber number+=1defsolve_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+=1print_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.85. 总结:
下面有一些总结,非常重要,希望对小伙伴们有所帮助:
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