我开始学习Python,我遇到过生成器函数,其中有yield语句。我想知道这些函数最擅长解决什么类型的问题。
当前回答
现实世界中的例子
假设你的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
上面的方法可以让您轻松地将缓冲与处理分开。消费者函数现在可以一个一个地获取值,而不用担心缓冲。
现实世界中的例子
假设你的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()
也适用于打印到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)
使用生成器的原因之一是为了使某些解决方案的解决方案更清晰。
另一种方法是一次处理一个结果,避免建立庞大的结果列表,否则无论如何都要分开处理。
如果你有这样一个fibonacci- to-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项列表,每次只创建一个值。在使用列表版本时,情况并非如此,在列表版本中,将首先创建列表。
由于没有提到生成器的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上谈发电机