我开始学习Python,我遇到过生成器函数,其中有yield语句。我想知道这些函数最擅长解决什么类型的问题。


当前回答

简单的解释是: 考虑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

使用生成器,您还可以对无限序列进行迭代,这在迭代容器时当然是不可能的。

其他回答

这里有一些很好的答案,但是,我也推荐完整阅读Python函数式编程教程,它有助于解释生成器的一些更有效的用例。

特别有趣的是,现在可以从生成器函数外部更新yield变量,因此可以用相对较少的工作创建动态和交织的协程。 更多信息请参见PEP 342:通过增强型生成器的协程。

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

假设我们正在读取一个文件,并且只想要以“##”开头的行。

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

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

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

reduce的例子类似。假设我们有一个文件,其中我们需要定位<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

同样,我们可以在一个合适的for循环中使用这个生成器。

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

其思想是,生成器函数允许我们过滤或减少序列,每次生成一个值的另一个序列。

现实世界中的例子

假设你的MySQL表中有1亿个域名,你想为每个域名更新Alexa排名。

你需要做的第一件事是从数据库中选择域名。

假设表名为domains,列名为domain。

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

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

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

在我们的第二批中,我们将处理接下来的1000行。第三批将从2001年到3000年,以此类推。

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

这是我们的生成器函数:

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

正如你所看到的,我们的函数总是得到结果。如果使用关键字return而不是yield,那么整个函数将在到达return时结束。

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 bufferedFetch():
  while True:
     buffer = getBigChunkOfData()
     # insert some code to break on 'end of data'
     for i in buffer:    
          yield i

上面的方法可以让您轻松地将缓冲与处理分开。消费者函数现在可以一个一个地获取值,而不用担心缓冲。

由于没有提到生成器的send方法,这里有一个例子:

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])

它展示了向运行中的生成器发送值的可能性。下面视频中关于生成器的更高级课程(包括解释的yield,并行处理的生成器,逃避递归限制等)

David Beazley在PyCon 2014上谈发电机