我开始学习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()

其他回答

我发现这个解释消除了我的疑虑。因为有一种可能,不知道发电机的人也不知道产量

返回

return语句是销毁所有局部变量并将结果值返回(返回)给调用者的语句。如果同一函数稍后被调用,该函数将获得一组新的变量。

收益率

但是,如果退出函数时局部变量没有被丢弃呢?这意味着我们可以从中断的地方恢复函数。这是引入生成器概念的地方,yield语句从函数停止的地方恢复。

  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语句使函数成为生成器函数。

因此生成器是创建迭代器的简单而强大的工具。它们像常规函数一样编写,但它们在想要返回数据时使用yield语句。每次调用next()时,生成器都会从停止的地方恢复(它会记住所有数据值以及最后执行的语句)。

也适用于打印到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项列表,每次只创建一个值。在使用列表版本时,情况并非如此,在列表版本中,将首先创建列表。

现实世界中的例子

假设你的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()

基本上避免回调函数时迭代输入维护状态。

请参阅这里和这里,了解使用生成器可以做什么。