生成器函数可以用来做什么?

2025-01-20 09:07:00
admin
原创
119
摘要:问题描述:我开始学习 Python,并且遇到了生成器函数,其中有一个yield语句。我想知道这些函数真正擅长解决哪些类型的问题。解决方案 1:生成器为您提供惰性求值。您可以通过迭代它们来使用它们,可以显式地使用“for”,也可以隐式地将其传递给任何迭代的函数或构造。您可以将生成器视为返回多个项目,就好像它们返...

问题描述:

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


解决方案 1:

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

生成器非常适合计算大量结果(尤其是涉及循环本身的计算),在这种情况下,您不知道是否需要所有结果,或者您不想同时为所有结果分配内存。或者,生成器使用另一个生成器或消耗其他资源的情况,如果这种情况尽可能晚地发生会更方便。

生成器的另一个用途(实际上是一样的)是用迭代代替回调。在某些情况下,您希望一个函数执行大量工作并偶尔向调用者报告。传统上,您会为此使用回调函数。您将此回调传递给工作函数,它会定期调用此回调。生成器的方法是,工作函数(现在是生成器)对回调一无所知,并且仅在需要报告某些内容时产生结果。调用者无需编写单独的回调并将其传递给工作函数,而是在生成器周围的一个小“for”循环中完成所有报告工作。

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

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

big_list = list(the_generator)

解决方案 2:

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

另一种方法是一次处理一个结果,避免构建庞大的结果列表(无论如何您都需要分开处理)。

如果你有一个像这样的斐波那契数列函数:

# function version
def fibon(n):
    a = b = 1
    result = []
    for i in range(n):
        result.append(a)
        a, b = b, a + b
    return result

您可以更轻松地编写如下函数:

# generator version
def fibon(n):
    a = b = 1
    for i in range(n):
        yield a
        a, b = b, a + b

该函数更加清晰。如果你像这样使用该函数:

for x in fibon(1_000_000):
    print(x),

在此示例中,如果使用生成器版本,则根本不会创建整个 1,000,000 个项目的列表,而只会一次创建一个值。使用列表版本时则不会出现这种情况,因为会先创建一个列表。

解决方案 3:

真实世界的例子

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

解决方案 4:

我发现这个解释消除了我的疑虑。因为有可能不知道的人Generators也不知道yield

返回

return 语句会销毁所有局部变量并将结果值返回给调用者。如果一段时间后调用同一个函数,该函数将获得一组全新的变量。

屈服

但是,如果我们退出函数时局部变量没有被丢弃,会怎么样呢?这意味着我们可以从上次中断的地方继续执行。这就是引入resume the function概念的地方,语句会从上次中断的地方继续执行。generators`yield`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

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

因此,生成器是创建迭代器的一个简单而强大的工具。它们的编写方式与常规函数类似,但yield只要它们想要返回数据,就会使用语句。每次调用 next() 时,生成器都会从上次中断的地方恢复(它会记住所有数据值以及最后执行了哪条语句)。

解决方案 5:

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

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

解决方案 6:

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

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

上面的代码让你可以轻松地将缓冲与处理分开。消费者函数现在可以逐个获取值,而不必担心缓冲。

解决方案 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 生成器 - 它们是什么?中得到了斐波那契的例子,只要稍加想象,你就可以想出很多其他的情况,在这些情况下,生成器可以成为for循环和其他传统迭代结构的绝佳替代品。

解决方案 8:

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

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

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

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:

一个可以使用生成器的实际示例是,如果您有某种形状,并且想要迭代其角、边或其他任何东西。对于我自己的项目(源代码在这里),我有一个矩形:

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()使用起来更优雅,__iter__因为我们可以在表达式中直接使用类实例名称for

解决方案 11:

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

请参阅此处和此处了解使用生成器可以做什么的概述。

解决方案 12:

由于尚未提及生成器的 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 上讨论生成器

解决方案 13:

这里有一些很好的答案,但是,我还建议完整阅读 Python函数式编程教程,它有助于解释生成器的一些更有效的用例。

  • 特别有趣的是,现在可以从生成器函数外部更新yield变量,从而可以以相对较少的努力创建动态和交织的协程。

  • 另请参阅PEP 342:通过增强生成器实现协程以获取更多信息。

解决方案 14:

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

  1. 客户端向服务器请求代理 URL

  2. 服务器开始加载目标url

  3. 服务器在获得结果后立即返回给客户端

解决方案 15:

一堆东​​西。任何时候你想生成一个项目序列,但又不想一次性将它们全部“具体化”到一个列表中。例如,你可以有一个返回素数的简单生成器:

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:

还适用于打印最多 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)
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2941  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1803  
  PLM(产品生命周期管理)系统在企业的产品研发、生产与管理过程中扮演着至关重要的角色。然而,在实际运行中,资源冲突是经常会遇到的难题。资源冲突可能导致项目进度延迟、成本增加以及产品质量下降等一系列问题,严重影响企业的效益与竞争力。因此,如何有效应对PLM系统中的资源冲突,成为众多企业关注的焦点。接下来,我们将详细探讨5...
plm项目管理系统   31  
  敏捷项目管理与产品生命周期管理(PLM)的融合,正成为企业在复杂多变的市场环境中提升研发效率、增强竞争力的关键举措。随着技术的飞速发展和市场需求的快速更迭,传统的研发流程面临着诸多挑战,而将敏捷项目管理理念融入PLM,有望在2025年实现研发流程的深度优化,为企业创造更大的价值。理解敏捷项目管理与PLM的核心概念敏捷项...
plm项目   31  
  模块化设计在现代产品开发中扮演着至关重要的角色,它能够提升产品开发效率、降低成本、增强产品的可维护性与可扩展性。而产品生命周期管理(PLM)系统作为整合产品全生命周期信息的关键平台,对模块化设计有着强大的支持能力。随着技术的不断发展,到 2025 年,PLM 系统在支持模块化设计方面将有一系列令人瞩目的技术实践。数字化...
plm软件   28  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用