代码之家  ›  专栏  ›  技术社区  ›  quamrana Ryuzaki L

您可以使用Python生成器函数做什么?

  •  197
  • quamrana Ryuzaki L  · 技术社区  · 17 年前

    我开始学习python,我遇到了生成器函数,它们中有一个yield语句。我想知道这些函数真正擅长解决哪些类型的问题。

    16 回复  |  直到 17 年前
        1
  •  222
  •   Thomas Wouters    14 年前

    生成器会给你懒惰的评价。您可以通过迭代它们来使用它们,或者显式地使用“for”,或者通过将其传递给任何迭代的函数或构造来隐式地使用它们。您可以将生成器视为返回多个项,就好像它们返回了一个列表,但是它们不是一次返回所有项,而是一个接一个地返回它们,并且生成器函数将暂停,直到请求下一个项为止。

    生成器很适合计算大型结果集(尤其是涉及循环本身的计算),在这些结果集中,您不知道是否需要所有结果,或者不希望同时为所有结果分配内存。或者在发电机使用的情况下 另一个 生成器,或者消耗一些其他资源,如果发生得越晚越好。

    生成器的另一个用途(实际上是相同的)是用迭代替换回调。在某些情况下,您希望函数做大量工作,并且偶尔向调用者报告。传统上,您将为此使用回调函数。您将此回调传递给工作函数,它将定期调用此回调。生成器的方法是,工作函数(现在是生成器)对回调一无所知,只要它想报告什么,就只生成。调用者不是编写单独的回调并将其传递给工作函数,而是在生成器周围的一个“for”循环中完成所有的报告工作。

    例如,假设您编写了一个“文件系统搜索”程序。您可以执行整个搜索,收集结果,然后一次显示一个结果。所有的结果都必须在显示第一个结果之前收集起来,并且所有的结果都将同时存储在内存中。或者您可以在找到结果的同时显示结果,这将更节省内存,对用户更友好。后者可以通过将结果打印函数传递给文件系统搜索函数来完成,也可以通过使搜索函数成为生成器并对结果进行迭代来完成。

    如果您想查看后两种方法的示例,请参见os.path.walk()(带回调的旧文件系统遍历函数)和os.walk()(新文件系统遍历生成器)。当然,如果您真的想将所有结果收集到一个列表中,生成器方法转换为大列表方法是很简单的:

    big_list = list(the_generator)
    
        2
  •  85
  •   nosklo    17 年前

    使用生成器的原因之一是为了使某些解决方案的解决方案更清晰。

    另一种方法是一次处理一个结果,避免构建一个庞大的结果列表,不管怎样,这些结果都会被分开处理。

    如果你有这样的斐波那契函数:

    # 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项列表,一次只创建一个值。在使用列表版本时,情况并非如此,首先会创建一个列表。

        3
  •  37
  •   Nickolay    17 年前

    请参阅中的“动机”部分 PEP 255 .

    生成器的一个不明显的用途是创建可中断的函数,它允许您在不使用线程的情况下“同时”执行更新UI或运行多个作业(实际上是交错的)。

        4
  •  35
  •   Peter Mortensen icecrime    7 年前

    我发现这个解释可以澄清我的怀疑。因为有一种可能性,不知道的人 Generators 也不知道 yield

    返回

    RETURN语句销毁所有局部变量,并将结果值返回(返回)给调用方。如果以后某个时候调用同一个函数,该函数将得到一组新的变量。

    但是如果在我们退出函数时局部变量没有被丢弃呢?这意味着我们可以 resume the function 我们离开的地方。这就是 generators 介绍和 产量 语句继续,其中 function 离开。

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

    这就是两者之间的区别 return 产量 python中的语句。

    yield语句使函数成为生成器函数。

    因此,生成器是创建迭代器的简单而强大的工具。它们被编写成常规函数,但是它们使用 产量 语句,只要它们想返回数据。每次调用next()时,生成器都会从停止的位置恢复(它会记住所有数据值以及上次执行的语句)。

        5
  •  28
  •   Peter Mortensen icecrime    7 年前

    现实世界的例子

    假设您的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 而不是 产量 ,则一旦达到返回值,整个函数将结束。

    return - returns only once
    yield - returns multiple times
    

    如果函数使用关键字 产量 然后是发电机。

    现在可以这样迭代:

    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()
    
        6
  •  26
  •   Rafał Dowgird    17 年前

    缓冲。当以大数据块的形式获取数据,但以小数据块的形式处理数据时,生成器可能有助于:

    def bufferedFetch():
      while True:
         buffer = getBigChunkOfData()
         # insert some code to break on 'end of data'
         for i in buffer:    
              yield i
    

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

        7
  •  21
  •   Peter Mortensen icecrime    7 年前

    我发现生成器在清理代码和提供一种非常独特的方法来封装和模块化代码方面非常有用。在这种情况下,您需要一些东西来根据其自身的内部处理不断地输出值,并且当需要从代码中的任何地方(而不仅仅是在循环或块中)调用某些东西时,生成器是 这个 使用的特点。

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

    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 Generators - What are they? 只要有一点想象力,你就可以想出很多其他的情况,发电机是一个伟大的替代品 for 循环和其他传统的迭代构造。

        8
  •  18
  •   dF.    17 年前

    简单解释: 考虑一下 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
    

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

        9
  •  12
  •   S.Lott    17 年前

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

    假设我们正在读取一个文件,只需要以“35;”开头的行。

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

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

        10
  •  8
  •   Pithikos    11 年前

    可以使用生成器的一个实际示例是,如果您有某种形状,并且希望在它的角、边或其他地方进行迭代。对于我自己的项目(源代码 here )我有一个矩形:

    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 把它叫做 for corner in myrect.iter_corners() . 使用起来更优雅 γ迭代 从那时起,我们可以直接在 for 表达式。

        11
  •  6
  •   MvdD    17 年前

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

    here here 了解如何使用生成器。

        12
  •  4
  •   Peter Mortensen icecrime    7 年前

    不过,这里有一些好的答案,我还建议您完整地阅读一下python Functional Programming tutorial 这有助于解释发电机的一些更有效的用例。

        13
  •  2
  •   Brian    17 年前

    当我们的Web服务器充当代理时,我使用生成器:

    1. 客户端从服务器请求代理的URL
    2. 服务器开始加载目标URL
    3. 服务器会在收到结果后立即返回给客户机。
        14
  •  2
  •   Peter Mortensen icecrime    7 年前

    由于没有提到生成器的发送方法,下面是一个示例:

    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 on generators at PyCon 2014

        15
  •  1
  •   Nick Johnson    17 年前

    成堆的东西。任何时候你想生成一系列的项目,但不想一次把它们“具体化”到一个列表中。例如,您可以有一个返回质数的简单生成器:

    def primes():
        primes_found = set()
        primes_found.add(2)
        yield 2
        for i in itertools.count(1):
            candidate = i * 2 + 1
            if not all(candidate % prime for prime in primes_found):
                primes_found.add(candidate)
                yield candidate
    

    然后可以使用它生成后续素数的产物:

    def prime_products():
        primeiter = primes()
        prev = primeiter.next()
        for prime in primeiter:
            yield prime * prev
            prev = prime
    

    这些都是非常简单的例子,但是您可以看到它如何对处理大型(可能是无限的)有用。没有提前生成数据集,这只是更明显的用途之一。

        16
  •  0
  •   Sébastien Wieckowski    8 年前

    也适用于打印最多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)