PS :坚持是原创的,只能说自己不要脸。 就当是波特吧。 希望能帮上忙
我们先来看看基础的做法。 重头戏在后面。
yield的英语单词是指生产,刚接触Python的时候非常困惑,不知道yield的用法。
但是,我大致知道yield可以用于填充函数返回值的数据。 例如,以下示例:
defaddlist(alist ) :foriinalist:yieldi 1
取出alist的各项,按入i 1。 然后,调用并取出每个项目。
a list=[ 1,2,3,4 ] forxinaddlist (a list ) :printx,
这确实是yield APP的一个例子,但通过阅读limodou文章《2.5版yield之学习心得》,自己反复体验,对yield有了全新的理解。
包含yield的函数
假设你看到一个函数包含yield。 这意味着此函数已经是Generator,其执行与其他常见函数有很大不同。 例如,以下简单函数:
默认(:打印‘tobe brave’yield 5h ) )。
h ) )之后,可以看到print语句没有执行。 这就是yield。 那么,如何让print语句执行呢? 这就是接下来要讨论的问题。 通过稍后的讨论和学习,我们将了解yield的工作原理。
2. yield是表达式
在Python2.5之前,yield是一个语句,但在当前的2.5中,yield是一个表达式(Expression ),如下所示:
m=yield5
公式yield 5的返回值代入m,所以认为m=5是错误的。 那么,如何获取(yield 5)的返回值呢? 需要后述的send(msg )方法。
next ) )通过句子看原理
那么,让我们弄清楚yield的结构吧。 我们知道我们上面的h (),因为有yield表达式,所以在被调用时不会执行。 因此,next ) )语句执行。 next ) )语句在下一个yield表达式之前恢复Generator的执行。 例如:
defh ) :打印‘汶川’yield 5打印‘fighting! (‘c=h () )。
c.next () )。
c.next () )被调用后,直到遇到yield 5为止h ) )开始执行,因此输出结果:
汶川
当我们再次调用c.next () )时,它会继续执行,直到找到下一个yield表达式。 因为后面没有yield,所以抛出异常:
汶川
好了!
跟踪后台(mostrecentcallast ) :
file '/home/evergreen/codes/yi dld.py ',line11,inc.next (
停止解释
4.send(msg )和next ) )。
next ) )了解如何执行包含yield的函数后,我们来看另一个非常重要的函数send(msg )。 其实next (和send ) )在某种意义上起着类似的作用。 不同的是,send ) )可以传递yield表达式的值,但next ) )不能传递特定的值,只能传递None。 所以,我们应该
c.next (和c.send )的作用相同。
让我们来看看这个例子
defh ) :打印‘汶川’,
m=yield5#Fighting! 打印
D=yield 12打印‘we are together! (‘c=h () )。
c.next(#为c.send(none ) c.send )《fighting! () ) ) #(yield5)式中有‘Fighting! 「
结果如下。
汶川加油!
在第一个调用中使用next (语句或send ) None。 不能使用send发送非None值。 发送非None值会导致错误,因为没有yield语句。
5.send(msg )和next ) )的返回值
send(msg )和next )具有返回值。 返回值是特殊的,返回以下yield表达式的参数: 例如,如果是yield 5,则返回5。 到此为止,你知道什么了吗? 在本文的第一个例子中,用for i in alist遍历Generator,但实际上每次都调用alist.Next () (每次都是alist.Next ) )的返回值是yield的参数继续上面的例子吧:
defh ) :打印‘汶川’,
m=yield5#Fighting! 打印
D=yield 12打印‘we are together! (‘c=h () )。
m=c.next () #m获取了yield5的参数值5d=c.send )‘fi’
ghting!‘)#d 获取了yield 12 的参数值12print‘We will never forget the date‘, m,‘.‘, d输出结果:
Wen Chuan Fighting!
We will never forget the date 5 . 12
6. throw() 与 close()中断 Generator
中断Generator是一个非常灵活的技巧,可以通过throw抛出一个GeneratorExit异常来终止Generator。Close()方法作用是一样的,其实内部它是调用了throw(GeneratorExit)的。我们看:
defclose(self):try:
self.throw(GeneratorExit)except(GeneratorExit, StopIteration):passelse:raiseRuntimeError("generator ignored GeneratorExit")#Other exceptions are not caught
因此,当我们调用了close()方法后,再调用next()或是send(msg)的话会抛出一个异常:
Traceback (most recent call last):
File"/home/evergreen/Codes/yidld.py", line14,ind=c.send(‘Fighting!‘)#d 获取了yield 12 的参数值12StopIteration
第二部分是干货:
协程与子例程
我们调用一个普通的Python函数时,一般是从函数的第一行代码开始执行,结束于return语句、异常或者函数结束(可以看作隐式的返回None)。一旦函数将控制权交还给调用者,就意味着全部结束。函数中做的所有工作以及保存在局部变量中的数据都将丢失。再次调用这个函数时,一切都将从头创建。
对于在计算机编程中所讨论的函数,这是很标准的流程。这样的函数只能返回一个值,不过,有时可以创建能产生一个序列的函数还是有帮助的。要做到这一点,这种函数需要能够“保存自己的工作”。
我说过,能够“产生一个序列”是因为我们的函数并没有像通常意义那样返回。return隐含的意思是函数正将执行代码的控制权返回给函数被调用的地方。而"yield"的隐含意思是控制权的转移是临时和自愿的,我们的函数将来还会收回控制权。
在Python中,拥有这种能力的“函数”被称为生成器,它非常的有用。生成器(以及yield语句)最初的引入是为了让程序员可以更简单的编写用来产生值的序列的代码。 以前,要实现类似随机数生成器的东西,需要实现一个类或者一个模块,在生成数据的同时保持对每次调用之间状态的跟踪。引入生成器之后,这变得非常简单。
为了更好的理解生成器所解决的问题,让我们来看一个例子。在了解这个例子的过程中,请始终记住我们需要解决的问题:生成值的序列。
注意:在Python之外,最简单的生成器应该是被称为协程(coroutines)的东西。在本文中,我将使用这个术语。请记住,在Python的概念中,这里提到的协程就是生成器。Python正式的术语是生成器;协程只是便于讨论,在语言层面并没有正式定义。
例子:有趣的素数
假设你的老板让你写一个函数,输入参数是一个int的list,返回一个可以迭代的包含素数
记住,迭代器(Iterable) 只是对象每次返回特定成员的一种能力。
你肯定认为"这很简单",然后很快写出下面的代码:
01
def get_primes(input_list):
02
result_list= list()
03
for elementin input_list:
04
if is_prime(element):
05
result_list.append()
06
07
return result_list
08
09
#
或者更好一些的...
10
11
def get_primes(input_list):
12
return (elementfor elementin input_listif is_prime(element))
13
14
#
下面是 is_prime 的一种实现...
15
16
def is_prime(number):
17
if number
>1:
18
if number== 2:
19
return True
20
if number% 2 == 0:
21
return False
22
for currentin range(3,int(math.sqrt(number)+ 1),2):
23
if number% current==0:
24
return False
25
return True
26
return False
上面 is_prime 的实现完全满足了需求,所以我们告诉老板已经搞定了。她反馈说我们的函数工作正常,正是她想要的。
处理无限序列
噢,真是如此吗?过了几天,老板过来告诉我们她遇到了一些小问题:她打算把我们的get_primes函数用于一个很大的包含数字的list。实际上,这个list非常大,仅仅是创建这个list就会用完系统的所有内存。为此,她希望能够在调用get_primes函数时带上一个start参数,返回所有大于这个参数的素数(也许她要解决 Project
Euler problem 10)。
我们来看看这个新需求,很明显只是简单的修改get_primes是不可能的。 自然,我们不可能返回包含从start到无穷的所有的素数的列表 (虽然有很多有用的应用程序可以用来操作无限序列)。看上去用普通函数处理这个问题的可能性比较渺茫
在我们放弃之前,让我们确定一下最核心的障碍,是什么阻止我们编写满足老板新需求的函数。通过思考,我们得到这样的结论:函数只有一次返回结果的机会,因而必须一次返回所有的结果。得出这样的结论似乎毫无意义;“函数不就是这样工作的么”,通常我们都这么认为的。可是,不学不成,不问不知,“如果它们并非如此呢?”
想象一下,如果get_primes可以只是简单返回下一个值,而不是一次返回全部的值,我们能做什么?我们就不再需要创建列表。没有列表,就没有内存的问题。由于老板告诉我们的是,她只需要遍历结果,她不会知道我们实现上的区别。
在我们放弃之前,让我们确定一下最核心的障碍,是什么阻止我们编写满足老板新需求的函数。通过思考,我们得到这样的结论:函数只有一次返回结果的机会,因而必须一次返回所有的结果。得出这样的结论似乎毫无意义;“函数不就是这样工作的么”,通常我们都这么认为的。可是,不学不成,不问不知,“如果它们并非如此呢?”
想象一下,如果get_primes可以只是简单返回下一个值,而不是一次返回全部的值,我们能做什么?我们就不再需要创建列表。没有列表,就没有内存的问题。由于老板告诉我们的是,她只需要遍历结果,她不会知道我们实现上的区别。
PS:先这样发了 后面还有