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

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

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

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

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

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

import numpy as np

list_test = [1, 2, 3, 4]
list_new = [x*x for x in list_test]#对于列表来说我们可以看到使用in来进行迭代
print("list_new: ", list_new)
list_new:  [1, 4, 9, 16]
#注意[]变成(),这样我们my_gen得到的并不是一个列表,而是一个生成器对象,从输出中可以看出
my_gen = (x*x for x in list_test)
print("my_gen: ", my_gen)
my_gen:  <generator object <genexpr> at 0x7f7eca184750>
#那么生成器对象要怎么使用呢?这里通过for循环来不停调用生成器,使用next来获得对应的值
#先简单有个印象,我们又使用了一个关键字,next!
for i in range(len(list_test)):
    print("my_gen: ", next(my_gen))
my_gen:  1
my_gen:  4
my_gen:  9
my_gen:  16
#定义一个简单的生成器函数,怎么看出来是生成器函数的呢?
#可以看到函数中含有关键字yield!!!!点题,本文的重点关键字来了
def g():
    print('1')
    x = yield 'hello'
    print('2', 'x= ', x)
    y = 5 + (yield x)
    print('3', 'y= ', y)

# 调用生成器函数,得到一个生成器对象   
f = g()    
print(f)
<generator object g at 0x7f7eca1624f8>
#使用next()得到返回的yield的值
next(f)
1
'hello'
# 出现第三个重点关键字,send,这里是强制设定 x = 5
f.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,在调用一个函数时,
#函数中定义的都是局部变量,在函数结束时,这些局部变量也就没有了,再次调用对应的函数,
#又会从函数的第一行开始运行

# 得到一个输入列表,里面包含有限长的数字,我们需要判断这些数字是否是素数,把素数组成一个
# 新的列表,进行返回
def get_primes(input_list):
    result_list = list()
    for element in input_list:
        if is_prime(element):
            result_list.append(element)
    return result_list


#素数的定义是,一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做素数
def is_prime(number):
    if number > 1:
        if number == 2:
            return True
        if number % 2 == 0:
            return False
        for current in range(3, int(np.sqrt(number)+1), 2):
            if number % current == 0:
                return False
        return True
    return False
        
#调用这个函数进行一个小小的实验,发现完全可以,那么还要做什么呢?
get_primes([x for x in range(10)])
[2, 3, 5, 7]

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

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

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

#这里我们再举一个小例子,可以使用for loop来获得对应的数据
def simple_generator_function():
    yield 1
    yield 2
    yield 3
    
for value in simple_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()
for value in our_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
def get_primes(number):
    while True:
        if is_prime(number):
            yield number
        number += 1
def solve_number_10():
    # She *is* working on Project Euler #10, I knew it!
    total = 2
    for next_prime in get_primes(3):
        if next_prime < 2000000:
            total += next_prime
        else:
            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…的最小素数。

def print_successive_primes(iterations, base=10):
    # like normal functions, a gnerator function
    # can be assigned to a variable
    prime_generator = get_primes(base)
    prime_generator.send(None)  
    for power in range(iterations):
        print(prime_generator.send(base**(power+1)))

            
def get_primes(number):
    while True:
        if is_prime(number):
            number = yield number
        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. 最后再举一个消费者和生产者的模型

import random 
def get_data():
    """return 3 random integers between 0 and 9"""
    return random.sample(range(10), 3)

def consume():
    """Display a running average across lists of integerss sent to it"""
    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 peroduce(sonsumer):
    """Produce a set of values and forwards them to 
    the pre-trained consumer function"""
    while True:
        data = get_data()
        print('Produced {}'.format(data))
        consumer.send(data)
        yield
        
if __name__=='__main__':
    consumer = consume()
    consumer.send(None)
    producer = peroduce(consumer)
    
    for _ in range(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

发布了349 篇原创文章 · 获赞 237 · 访问量 65万+

猜你喜欢

转载自blog.csdn.net/Felaim/article/details/104820305