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


当前回答

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

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

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

由于没有提到生成器的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上谈发电机

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

返回

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()时,生成器都会从停止的地方恢复(它会记住所有数据值以及最后执行的语句)。

你可以使用生成器的一个实际例子是,如果你有某种形状,你想要遍历它的角、边或其他地方。对于我自己的项目(源代码在这里),我有一个矩形:

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

现在我可以创建一个矩形,并在它的角上循环:

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

除了__iter__,你可以有一个方法iter_corners,并在myrect.iter_corners()中使用for corner来调用它。使用__iter__更优雅,因为我们可以在for表达式中直接使用类实例名。

我发现生成器非常有助于清理代码,并为您提供了一种非常独特的方式来封装和模块化代码。如果您需要某些东西根据自己的内部处理不断地输出值,并且需要从代码中的任何地方调用该东西(而不仅仅是在循环或块中),则可以使用生成器。

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

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

生成器的可爱之处在于,它们封装了状态,而不必经历创建对象的繁琐过程。考虑它们的一种方法是将它们视为记住其内部状态的“函数”。

我从Python生成器中得到了斐波那契函数的例子——它们是什么?只要有一点想象力,您就可以想出很多其他情况,在这些情况下,生成器可以很好地替代for循环和其他传统迭代结构。