你可以使用什么Python生成器函数?

我开始学习Python,并且遇到了生成器函数,那些函数有一个yield语句。 我想知道这些function真正擅长解决什么types的问题。

发电机给你懒评估。 可以通过遍历它们来使用它们,或者显式地使用“for”,或者通过将其传递给迭代的任何函数或结构来隐式地使用它们。 您可以将生成器视为返回多个项目,就好像它们返回一个列表一样,而不是一次返回它们,而是一个接一个地返回它们,并且生成器函数暂停,直到请求下一个项目。

生成器对计算大量结果(特别是涉及循环的计算)非常有用,在这些结果中,您不知道是否需要所有结果,或者哪里不想同时为所有结果分配内存。 或者在发电机使用另一台发电机的情况下,或者消耗其他一些资源,如果这种情况发生的时间较晚,则更为方便。

生成器的另一个用法(这是真的一样)是用迭代replacecallback。 在某些情况下,你需要一个function来做很多工作,偶尔向调用者报告。 传统上你会使用callback函数。 您将此callback传递给工作函数,并定期调用此callback函数。 生成器方法是工作函数(现在是一个生成器)对callback一无所知,只要它想要报告什么就产生。 调用者不是写一个单独的callback函数,而是将其传递给工作函数,而是在生成器周围的一个“for”循环中完成所有的报告工作。

例如,假设你写了一个“文件系统search”程序。 您可以执行整个search,收集结果,然后一次显示一个。 所有的结果都必须在你展示第一个结果之前收集,并且所有的结果都会同时在内存中。 或者您可以在find它们的同时显示结果,这对用户来说会更有效率,更友好。 后者可以通过将结果打印function传递给文件系统searchfunction来完成,也可以通过将searchfunction作为生成器并迭代结果来完成。

如果你想看到后两种方法的例子,请参阅os.path.walk()(带有callback的旧文件系统行走function)和os.walk()(新文件系统行走生成器)。当然,如果你真的想收集一个列表中的所有结果,生成器方法是微不足道的转换为大列表方法:

big_list = list(the_generator) 

使用生成器的原因之一是使解决scheme更清晰一些。

另一种方法是一次处理一个结果,避免build立大量的结果列表,无论如何你都要处理这些结果。

如果你有这样一个斐波那契函数:

 # function version def fibon(n): a = b = 1 result = [] for i in xrange(n): result.append(a) a, b = b, a + b return result 

你可以更容易地写这个function:

 # generator version def fibon(n): a = b = 1 for i in xrange(n): yield a a, b = b, a + b 

function更清晰。 如果你使用这样的function:

 for x in fibon(1000000): print x, 

在这个例子中,如果使用生成器版本,则完全不会创build整个1000000个项目列表,每次只有一个值。 当使用列表版本时,情况并非如此,列表将首先被创build。

请参阅PEP 255中的“动机”部分。

生成器的一个非显而易见的用途是创build可中断的函数,它可以让你做一些事情,比如更新UI或者在不使用线程的情况下“同时”(交织,实际)运行多个作业。

我觉得这个解释清楚了我的怀疑。 因为不知道Generators人有可能不知道yield

返回

return语句是所有局部variables被销毁的地方,并且返回(返回)给调用者的结果值。 如果稍后调用同一个函数,函数将会得到一组新的variables。

产量

但是如果当我们退出一个函数时局部variables不会被丢弃呢? 这意味着我们可以从我们离开的地方resume the function 。 这是引入generators的概念的地方, yield functionfunction中断的地方恢复。

  def generate_integers(N): for i in xrange(N): yield i 

  In [1]: gen = generate_integers(3) In [2]: gen <generator object at 0x8117f90> In [3]: gen.next() 0 In [4]: gen.next() 1 In [5]: gen.next() 

所以这就是Python中的return和yield语句的区别。

Yield语句是使函数成为生成器函数的原因。

所以生成器是创build迭代器的一个简单而强大的工具。 它们像常规函数一样编写,但是只要他们想返回数据就使用yield语句。 每次next()被调用时,发生器都会从其停止的位置(它记住所有的数据值以及上一次执行的语句)恢复。

缓冲。 当以大块方式获取数据是有效的,但是以小块方式处理数据时,发生器可能会有所帮助:

 def bufferedFetch(): while True: buffer = getBigChunkOfData() # insert some code to break on 'end of data' for i in buffer: yield i 

以上可以让你轻松地将缓冲与处理分开。 消费者function现在可以逐个获取值,而不用担心缓冲。

真实世界的例子

假设您的MySQL表中有1亿个域名,并且您想更新每个域名的alexa排名。

首先你需要从数据库中select你的域名。

可以说你的表名是domains ,列名是domain

如果你使用SELECT domain FROM domains它将返回1亿行,这将消耗大量的内存。 所以你的服务器可能会崩溃

所以你决定分批运行这个程序。 比方说我们的批量大小是1000。

在我们的第一批中,我们将查询前1000行,检查每个域的alexa排名并更新数据库行。

在第二批我们将在接下来的1000行上工作。 在我们的第三批将从2001年到3000年,等等。

现在我们需要一个生成我们批次的生成器函数。

这是我们的发电机function

 def ResultGenerator(cursor, batchsize=1000): while True: results = cursor.fetchmany(batchsize) if not results: break for result in results: yield result 

正如你可以看到我们的function保持yield的结果。 如果使用关键字return而不是yield ,那么整个函数一旦返回就会结束

 return - returns only once yield - returns multiple times 

如果一个函数使用关键字yield那么它是一个生成器。

现在你可以像这样迭代

 db = MySQLdb.connect(host="localhost", user="root", passwd="root", db="domains") cursor = db.cursor() cursor.execute("SELECT domain FROM domains") for result in ResultGenerator(cursor): doSomethingWith(result) db.close() 

我发现生成器在清理代码方面非常有帮助,并给你一个非常独特的方法来封装和模块化代码。 在某些情况下,你需要根据自己的内部处理来不断地吐出一些值,当你需要从代码中的任何地方(而不仅仅是在一个循环或一个块内)调用某些东西的时候,使用。

一个抽象的例子将是一个斐波那契数字生成器,它不会生存在一个循环中,当它从任何地方被调用时,将总是按顺序返回下一个数字:

 def fib(): first=0 second=1 yield first yield second while 1: next=first+second yield next first=second second=next fibgen1=fib() fibgen2=fib() 

现在你有两个斐波那契数字发生器对象,你可以从你的代码中的任何地方调用,他们将总是返回更大的斐波那契数字,顺序如下:

 >>> fibgen1.next(); fibgen1.next(); fibgen1.next(); fibgen1.next() 0 1 1 2 >>> fibgen2.next(); fibgen2.next() 0 1 >>> fibgen1.next(); fibgen1.next() 3 5 

关于生成器的可爱之处在于它们封装了状态,而不必通过创build对象的环节。 一种思考它们的方式就是记住它们内部状态的“function”。

我从http://www.neotitans.com/resources/python/python-generators-tutorial.html得到了斐波纳契的例子,并且有一点想象力,你可以想出许多其他情况,在这些情况下,发电机组是一个很好的select以for循环和其他传统的迭代构造。;

简单的解释:考虑一个for语句

 for item in iterable: do_stuff() 

很多时候, iterable中的所有项目不需要从一开始就在那里,但是可以在需要时随时生成。 这可以在两个方面更有效率

  • 空间(你永远不需要同时存储所有的项目)和
  • 时间(在需要所有项目之前迭代可以完成)。

其他时候,你甚至不知道所有的项目提前。 例如:

 for command in user_input(): do_stuff_with(command) 

你无法事先知道所有用户的命令,但是如果你有一个生成器处理你的命令,你可以使用这样一个好的循环:

 def user_input(): while True: wait_for_command() cmd = get_command() yield cmd 

有了生成器,你也可以对无限序列进行迭代,迭代容器当然是不可能的。

我最喜欢的用途是“过滤”和“减less”操作。

比方说,我们正在阅读一个文件,只需要以“##”开头的行。

 def filter2sharps( aSequence ): for l in aSequence: if l.startswith("##"): yield l 

然后,我们可以在正确的循环中使用生成器函数

 source= file( ... ) for line in filter2sharps( source.readlines() ): print line source.close() 

减less的例子是类似的。 比方说,我们有一个文件,我们需要定位<Location>...</Location>行的块。 [不是HTML标签,而是看起来像标签的行。]

 def reduceLocation( aSequence ): keep= False block= None for line in aSequence: if line.startswith("</Location"): block.append( line ) yield block block= None keep= False elif line.startsWith("<Location"): block= [ line ] keep= True elif keep: block.append( line ) else: pass if block is not None: yield block # A partial block, icky 

再次,我们可以在适当的循环中使用这个生成器。

 source = file( ... ) for b in reduceLocation( source.readlines() ): print b source.close() 

这个想法是,一个生成器函数允许我们过滤或减less一个序列,一次产生另一个序列的一个值。

你可以使用一个发生器的一个实际例子是,如果你有某种形状,并且想要遍历它的angular落,边缘或其他东西。 对于我自己的项目(源代码在这里 ),我有一个矩形:

 class Rect(): def __init__(self, x, y, width, height): self.l_top = (x, y) self.r_top = (x+width, y) self.r_bot = (x+width, y+height) self.l_bot = (x, y+height) def __iter__(self): yield self.l_top yield self.r_top yield self.r_bot yield self.l_bot 

现在我可以创build一个矩形,并在其angular落循环:

 myrect=Rect(50, 50, 100, 100) for corner in myrect: print(corner) 

而不是__iter__你可以有一个方法iter_cornersfor corner in myrect.iter_corners() 。 使用__iter__更优雅,因为我们可以直接在forexpression式中使用类实例名称。

迭代input维护状态时基本避免callback函数。

看看这里和这里的概述,可以使用发电机做什么。

这里有一些很好的答案,但是,我还build议阅读一下python 函数式编程教程 ,以帮助解释一些更有效的生成器用例。

  • 特别有趣的是,现在可以从生成器函数外部更新yieldvariables ,因此可以用相对小的努力来创builddynamic和交织的协程。
  • 另请参阅PEP 342:通过增强型发生器获取更多信息。

当我们的Web服务器充当代理时,我使用生成器:

  1. 客户端从服务器请求代理的URL
  2. 服务器开始加载目标url
  3. 服务器返回结果给客户端一旦得到它们

由于这里没有提到发生器的发送方法,所以就是一个例子:

 def test(): for i in xrange(5): val = yield print(val) t = test() # proceed to yield statement next(t) # send value to yield t.send(1) t.send('2') t.send([3]) 

它显示了向运行中的发生器发送值的可能性。下面的video中的发生器的更高级的过程(包括来自发生器的产量,并行处理的发生器,逃逸recursion限制等)

David Beazley在PyCon 2014的发电机上的演讲

一堆东​​西。 任何时候你想要生成一系列的项目,但不希望一次性将它们“物化”成一个列表。 例如,你可以有一个简单的生成器返回素数:

 def primes(): primes_found = set() primes_found.add(2) yield 2 for i in itertools.count(1): candidate = i * 2 + 1 if not all(candidate % prime for prime in primes_found): primes_found.add(candidate) yield candidate 

然后,您可以使用它来生成后续素数的产品:

 def prime_products(): primeiter = primes() prev = primeiter.next() for prime in primeiter: yield prime * prev prev = prime 

这些都是相当简单的例子,但是你可以看到它是如何处理大的(可能是无限的!)数据集,而不提前生成它们,这只是一个更明显的用途。

同样适用于打印最多n的素数:

 def genprime(n=10): for num in range(3, n+1): for factor in range(2, num): if num%factor == 0: break else: yield(num) for prime_num in genprime(100): print(prime_num)