在开始课程之前,我要求学生们填写一份调查表,这个调查表反映了它们对Python中一些概念的理解情况。一些话题("if/else控制流" 或者 "定义和使用函数")对于大多数学生是没有问题的。但是有一些话题,大多数学生只有很少,或者完全没有任何接触,尤其是“生成器和yield关键字”。我猜这对大多数新手Python程序员也是如此。
有事实表明,在我花了大功夫后,有些人仍然不能理解生成器和yield关键字。我想让这个问题有所改善。在这篇文章中,我将解释yield关键字到底是什么,为什么它是有用的,以及如何来使用它。
注意:最近几年,生成器的功能变得越来越强大,它已经被加入到了PEP。在我的下一篇文章中,我会通过协程(coroutine),协同式多任务处理(cooperative multitasking),以及异步IO(asynchronous I/O)(尤其是GvR正在研究的 "tulip" 原型的实现)来介绍yield的真正威力。但是在此之前,我们要对生成器和yield有一个扎实的理解.
在Python中,拥有这种能力的“函数”被称为生成器,它非常的有用。生成器(以及yield语句)最初的引入是为了让程序员可以更简单的编写用来产生值的序列的代码。 以前,要实现类似随机数生成器的东西,需要实现一个类或者一个模块,在生成数据的同时保持对每次调用之间状态的跟踪。引入生成器之后,这变得非常简单。
为了更好的理解生成器所解决的问题,让我们来看一个例子。在了解这个例子的过程中,请始终记住我们需要解决的问题:生成值的序列。
注意:在Python之外,最简单的生成器应该是被称为协程(coroutines)的东西。在本文中,我将使用这个术语。请记住,在Python的概念中,这里提到的协程就是生成器。Python正式的术语是生成器;协程只是便于讨论,在语言层面并没有正式定义。
假设你的老板让你写一个函数,输入参数是一个int的list,返回一个可以迭代的包含素数1 的结果。
记住,迭代器(Iterable) 只是对象每次返回特定成员的一种能力。
你肯定认为"这很简单",然后很快写出下面的代码:
def get_primes(input_list): result_list = list() for element in input_list: if is_prime(element): result_list.append() return result_list # 或者更好一些的... def get_primes(input_list): return (element for element in input_list if is_prime(element)) # 下面是 is_prime 的一种实现... def is_prime(number): if number > 1: if number == 2: return True if number % 2 == 0: return False for current in range(3, int(math.sqrt(number) + 1), 2): if number % current == 0: return False return True return False
上面 is_prime 的实现完全满足了需求,所以我们告诉老板已经搞定了。她反馈说我们的函数工作正常,正是她想要的。
噢,真是如此吗?过了几天,老板过来告诉我们她遇到了一些小问题:她打算把我们的get_primes函数用于一个很大的包含数字的list。实际上,这个list非常大,仅仅是创建这个list就会用完系统的所有内存。为此,她希望能够在调用get_primes函数时带上一个start参数,返回所有大于这个参数的素数(也许她要解决 Project Euler problem 10)。
我们来看看这个新需求,很明显只是简单的修改get_primes是不可能的。 自然,我们不可能返回包含从start到无穷的所有的素数的列表 (虽然有很多有用的应用程序可以用来操作无限序列)。看上去用普通函数处理这个问题的可能性比较渺茫。
不幸的是,这样做看上去似乎不太可能。即使是我们有神奇的函数,可以让我们从n遍历到无限大,我们也会在返回第一个值之后卡住:
def get_primes(start): for element in magical_infinite_range(start): if is_prime(element): return element假设这样去调用get_primes:
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
显然,在get_primes中,一上来就会碰到输入等于3的,并且在函数的第4行返回。与直接返回不同,我们需要的是在退出时可以为下一次请求准备一个值。
不过函数做不到这一点。当函数返回时,意味着全部完成。我们保证函数可以再次被调用,但是我们没法保证说,“呃,这次从上次退出时的第4行开始执行,而不是常规的从第一行开始”。函数只有一个单一的入口:函数的第1行代码。
这类问题极其常见以至于Python专门加入了一个结构来解决它:生成器。一个生成器会“生成”值。创建一个生成器几乎和生成器函数的原理一样简单。
一个生成器函数的定义很像一个普通的函数,除了当它要生成一个值的时候,使用yield关键字而不是return。如果一个def的主体包含yield,这个函数会自动变成一个生成器(即使它包含一个return)。除了以上内容,创建一个生成器没有什么多余步骤了。
生成器函数返回生成器的迭代器。这可能是你最后一次见到“生成器的迭代器”这个术语了, 因为它们通常就被称作“生成器”。要注意的是生成器就是一类特殊的迭代器。作为一个迭代器,生成器必须要定义一些方法(method),其中一个就是__next__()。如同迭代器一样,我们可以使用next()函数来获取下一个值。
(next()会操心如何调用生成器的__next__()方法)。既然生成器是一个迭代器,它可以被用在for循环中。
每当生成器被调用的时候,它会返回一个值给调用者。在生成器内部使用yield来完成这个动作(例如yield 7)。为了记住yield到底干了什么,最简单的方法是把它当作专门给生成器函数用的特殊的return(加上点小魔法)。**
yield就是专门给生成器用的return(加上点小魔法)。
下面是一个简单的生成器函数:
>>> 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 >>> next(our_generator) 2 >>> next(our_generator) 3
那么神奇的部分在哪里?我很高兴你问了这个问题!当一个生成器函数调用yield,生成器函数的“状态”会被冻结,所有的变量的值会被保留下来,下一行要执行的代码的位置也会被记录,直到再次调用next()。一旦next()再次被调用,生成器函数会从它上次离开的地方开始。如果永远不调用next(),yield保存的状态就被无视了。
我们来重写get_primes()函数,这次我们把它写作一个生成器。注意我们不再需要magical_infinite_range函数了。使用一个简单的while循环,我们创造了自己的无穷串列。
def get_primes(number): while True: if is_prime(number): yield number number += 1
评论删除后,数据将无法恢复
评论(15)
引用来自“LinuxQueen”的评论
引用来自“se77en.cc”的评论
引用来自“renwofei423”的评论
引用来自“se77en.cc”的评论
引用来自“se77en.cc”的评论
引用来自“renwofei423”的评论
引用来自“LinuxQueen”的评论
引用来自“renwofei423”的评论
引用来自“crab2313”的评论
引用来自“renwofei423”的评论
引用来自“crab2313”的评论
一楼翻译太奇芭了
晕死。。。哈哈
我也新翻译了几段,不过只能保证语句通顺,技术上没有问题。
我提交的这篇文章来自Python-weekly推荐哦
传说中的盖楼嘛
引用来自“se77en.cc”的评论
引用来自“renwofei423”的评论
引用来自“se77en.cc”的评论
引用来自“se77en.cc”的评论
引用来自“renwofei423”的评论
引用来自“LinuxQueen”的评论
引用来自“renwofei423”的评论
引用来自“crab2313”的评论
引用来自“renwofei423”的评论
引用来自“crab2313”的评论
一楼翻译太奇芭了
晕死。。。哈哈
我也新翻译了几段,不过只能保证语句通顺,技术上没有问题。
我提交的这篇文章来自Python-weekly推荐哦
引用来自“renwofei423”的评论
引用来自“se77en.cc”的评论
引用来自“se77en.cc”的评论
引用来自“renwofei423”的评论
引用来自“LinuxQueen”的评论
引用来自“renwofei423”的评论
引用来自“crab2313”的评论
引用来自“renwofei423”的评论
引用来自“crab2313”的评论
一楼翻译太奇芭了
晕死。。。哈哈
我也新翻译了几段,不过只能保证语句通顺,技术上没有问题。
我提交的这篇文章来自Python-weekly推荐哦
引用来自“se77en.cc”的评论
引用来自“se77en.cc”的评论
引用来自“renwofei423”的评论
引用来自“LinuxQueen”的评论
引用来自“renwofei423”的评论
引用来自“crab2313”的评论
引用来自“renwofei423”的评论
引用来自“crab2313”的评论
一楼翻译太奇芭了
晕死。。。哈哈
我也新翻译了几段,不过只能保证语句通顺,技术上没有问题。
我提交的这篇文章来自Python-weekly推荐哦
引用来自“se77en.cc”的评论
引用来自“renwofei423”的评论
引用来自“LinuxQueen”的评论
引用来自“renwofei423”的评论
引用来自“crab2313”的评论
引用来自“renwofei423”的评论
引用来自“crab2313”的评论
一楼翻译太奇芭了
晕死。。。哈哈
我也新翻译了几段,不过只能保证语句通顺,技术上没有问题。
引用来自“renwofei423”的评论
引用来自“LinuxQueen”的评论
引用来自“renwofei423”的评论
引用来自“crab2313”的评论
引用来自“renwofei423”的评论
引用来自“crab2313”的评论
一楼翻译太奇芭了
晕死。。。哈哈
我也新翻译了几段,不过只能保证语句通顺,技术上没有问题。
引用来自“LinuxQueen”的评论
引用来自“renwofei423”的评论
引用来自“crab2313”的评论
引用来自“renwofei423”的评论
引用来自“crab2313”的评论
一楼翻译太奇芭了
晕死。。。哈哈
我也新翻译了几段,不过只能保证语句通顺,技术上没有问题。
引用来自“renwofei423”的评论
引用来自“crab2313”的评论
引用来自“renwofei423”的评论
引用来自“crab2313”的评论
一楼翻译太奇芭了
晕死。。。哈哈
我也新翻译了几段,不过只能保证语句通顺,技术上没有问题。