生成器函数可以用来做什么?
我刚开始学习Python,遇到了生成器函数,也就是里面有yield语句的那些函数。我想知道这些函数特别擅长解决什么样的问题。
16 个回答
现实世界的例子
假设你在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
如你所见,我们的函数不断地 yield
结果。如果你用 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()
使用生成器的一个原因是让某些解决方案变得更清晰。
另一个原因是可以一次处理一个结果,避免生成庞大的结果列表,而这些结果你本来就会分开处理。
比如你有一个计算斐波那契数列到n的函数,像这样:
# function version
def fibon(n):
a = b = 1
result = []
for i in xrange(n):
result.append(a)
a, b = b, a + b
return result
你可以更简单地把这个函数写成这样:
# generator version
def fibon(n):
a = b = 1
for i in xrange(n):
yield a
a, b = b, a + b
这样写的函数更清晰。而且如果你这样使用这个函数:
for x in fibon(1000000):
print x,
在这个例子中,如果使用生成器版本,就不会一次性创建出1000000个项目的列表,而是每次只生成一个值。而如果使用列表版本,就会先创建一个完整的列表。
生成器提供了一种懒惰的计算方式。你可以通过循环来使用它们,可以用'for'循环显式地迭代,也可以隐式地把它们传给任何需要迭代的函数或结构。你可以把生成器想象成返回多个项目,就像返回一个列表,但它们不是一次性返回所有,而是一个一个地返回,每次返回后生成器会暂停,直到下一个项目被请求。
生成器非常适合计算大量结果的情况,特别是当你不确定是否需要所有结果,或者不想一次性占用太多内存的时候。还有一些情况是生成器会使用另一个生成器,或者消耗其他资源,这种情况下,尽可能晚地进行这些操作会更方便。
生成器的另一个用途(其实是同样的道理)是用迭代来替代回调。在某些情况下,你希望一个函数做很多工作,并偶尔向调用者报告进展。传统上,你会使用回调函数来实现这一点。你把这个回调函数传给工作函数,工作函数会定期调用这个回调。而使用生成器的方法是,工作函数(现在是一个生成器)对回调一无所知,只是在需要报告的时候“产出”结果。调用者不需要写一个单独的回调函数并传给工作函数,而是可以在一个小的'for'循环中处理所有的报告工作。
举个例子,假设你写了一个“文件系统搜索”程序。你可以先完成整个搜索,收集所有结果,然后逐个显示它们。这样的话,所有结果都必须在显示第一个之前收集好,并且所有结果会同时占用内存。或者你可以在找到结果的同时就显示它们,这样会更节省内存,也更友好于用户。后者可以通过把结果打印的函数传给文件系统搜索函数来实现,或者直接把搜索函数写成生成器,然后对结果进行迭代。
如果你想看看这两种方法的例子,可以查看os.path.walk()(旧的文件系统遍历函数,使用回调)和os.walk()(新的文件系统遍历生成器)。当然,如果你真的想把所有结果收集到一个列表中,使用生成器的方法也很简单,可以轻松转换成大列表的方法:
big_list = list(the_generator)