首先LZ先问下哈,有多少小伙伴了解python当中的“generators”和关键字“yield”呢?
Python生成器是通过调用yield返回生成器迭代器(只是我们可以迭代的对象)的函数。 可以用一个值调用yield,在这种情况下,该值被视为“生成的”值。 下次在生成器迭代器上调用next()时(例如,在for循环的下一步中),生成器从其调用yield的位置恢复执行,而不是从函数的开头恢复执行。 像局部变量的值一样,所有状态都将恢复,并且生成器将继续执行,直到下一次调用yield为止。
PS:好像还不是很理解的样子,不要紧,我们慢慢来理解一下
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: 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)
#使用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)
in
----> 1 f.send(2)
StopIteration:
#通常我们写的函数都叫做函数,一般都会有个关键字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)
in
----> 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语句,因此,如果我们发送真实值,将没有任何东西可以“接收”它。 一旦生成器启动,我们就可以像上面一样发送值。
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
下面有一些总结,非常重要,希望对小伙伴们有所帮助:
1.生成器的目的是为了生成一系列数值(斐波那契数列等)
2.yield其实类似与函数的return,但是生成器函数中的变量都会对应保存
3.生成器就是一种特殊的迭代器
4.像迭代器一样,我们可以使用next()从生成器中获取下一个值,或者使用send来强制传输对应的值
5.我们也可以通过for loop来隐式的调用next()获取下一个值
6.使用生成的好处是可以节省内存,并不需要一次性返回所有的值,而是在需要的时候获取对应的数据,这就让LZ联想到了其实在深度学习,我们在读取数据时,经常会需要读取海量的数据,数据量太大的情况下是没有办法一次性全部读进内存的,最后的方法就是使用这种生成器等类似的方式
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