Python 生成器函数:深入理解与应用案例

在Python编程中,生成器函数是一种非常强大且高效的工具,它们允许你以一种惰性求值(lazy evaluation)的方式生成序列。与普通的函数不同,生成器函数使用yield关键字一次返回一个值,并在下一次调用时从上次返回的位置继续执行,而不是从头开始。这种机制使得生成器在处理大量数据时能够显著节省内存和计算资源。本文将深入探讨Python生成器函数的工作原理,并通过几个实际案例展示其强大的应用。

首先,让我们通过一个简单的例子来理解生成器函数的基本概念。

在这个例子中,simple_generator函数定义了三个yield语句,每次调用next()(或在for循环中隐式调用)时,函数会从上次yield的位置继续执行,直到遇到下一个yield或函数结束。这种机制使得函数能够“记住”其执行状态,非常适合处理需要逐步生成数据而不需要一次性加载全部数据的场景。

  1. 节省内存:由于生成器是按需生成数据,它们不会像列表那样一次性占用大量内存。
  2. 延迟计算:只有在需要时才进行计算,这对于处理无限序列或大型数据集特别有用。
  3. 可迭代协议:生成器对象实现了Python的迭代协议,可以像列表那样在for循环中使用。

斐波那契数列是一个经典的数学序列,每个数是前两个数的和。使用生成器函数可以高效地生成这个序列。

在处理大型文件时,一次性读入所有内容可能会导致内存溢出。使用生成器函数可以逐行读取文件,有效避免这个问题。

生成器特别适合生成无限序列,因为你可以控制何时停止迭代,而不需要在内存中存储整个序列。

注意,在这个例子中,我们使用了itertools.islice来从无限生成器中截取有限数量的元素,这是一种处理无限序列的常用技巧。

生成器还可以用来模拟复杂的迭代逻辑,比如深度优先搜索(DFS)或广度优先搜索(BFS)。以下是一个简单的DFS实现示例:

生成器函数是Python中一个非常强大的特性,它们提供了一种高效、灵活的方式来处理序列数据。无论是生成无限序列、处理大型文件、还是模拟复杂的迭代逻辑,生成器都能展现出其独特的优势。通过理解和运用生成器函数,你可以编写出更加高效、内存友好的Python代码。希望本文能帮助你更好地掌握生成器函数,并在实际项目中发挥其潜力。

看见函数app专业的函数绘制工具

看见函数app是一个专业的函数绘制工具,函数是数学中非常重要的内容,许多同学会觉得手动化函数太麻烦,那么你就非常需要这款手机应用,它支持加、减、乘、除、幂函数、指数函数、对数函数、三角函数,反三角函数等所有初等函数,只需要输入函数方程式就可以会自自动绘制出函数图形,让函数更加直观的展现在你的面前,从而更好的理解和学习函数。是学习函数的好帮手,你也可以用它来绘制出一些特殊图形的函数图像。

软件特色

1、新增:新函数和以保存的函数

2、删除:删除编辑过的函数

3、修改:是否显示公式,设置字体颜色

4、存储:保存与删除函数

软件功能

1、将各种数学函数作图显示出来,可供学习、科学分析、趋势判断等用途

2、内置各种数学函数,方便选取。支持各种函数组合成复杂函数,可自行定义。图像可缩放、平移、指定区间查看

3、可将函数图像导出保存为图片

操作说明

1、该程序根据用户输入的函数方程绘制函数曲线。

2、支持加、减、乘、除、幂函数、指数函数、对数函数、三角函数,反三角函数等所有初等函数,

3、另外还支持绝对值、最小、最大等函数。

4、绘制的函数曲线可以平移和缩放。放大时最小坐标单位为0.001。

原文地址:http://www.3h3.com/az/193686.html

Python 生成器:高效的数据生成机制

在 Python 中,我们可以通过列表生成式便捷地创建一个列表。然而,列表的创建存在一定局限性,由于受到内存的限制,其容量必然是有限的。想象一下,如果要创建一个包含 100 万个元素的列表,这不仅会占用极大的存储空间,而且在很多实际应用场景中,可能我们仅仅需要访问前面的几个元素,如此一来,后面绝大多数元素所占用的空间就白白浪费了,这显然不是一种高效的做法。

那么,有没有一种更好的方式呢?如果列表元素能够依照某种既定的算法推算出来,是不是可以在循环的过程中按需去推算后续元素,而不必事先创建完整的列表呢?这样就能节省大量的内存空间啦。在 Python 里,这种一边循环一边计算的巧妙机制,就是我们所说的生成器(generator)。

创建生成器的方法有多种,其中一种十分简单,只需把列表生成式外层的方括号 [] 改成圆括号 (),就能轻松创建一个生成器了。我们来看下面的示例对比:

运行上述代码,L 的输出结果是 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81],它是一个常规的列表,而 g 输出的是 <generator object <genexpr> at 0x1022ef630>,这表明 g 是一个生成器对象,二者的区别仅在于最外层符号的不同,但本质上代表了两种不同的数据生成和存储方式。

既然创建了生成器,那如何获取它所生成的每一个元素呢?我们可以通过 next() 函数来获取生成器的下一个返回值,示例如下:

每次调用 next(g),生成器就会按照其内部的算法计算出下一个元素的值并返回。不过需要注意的是,当生成器计算到最后一个元素,没有更多元素可供生成时,再调用 next() 函数就会抛出 StopIteration 的错误,如下所示:

最后一次调用 next(g) 时,就会出现类似下面的报错信息:

虽然可以使用 next() 函数逐个获取生成器的元素,但不断手动调用 next() 函数实在不太方便,也不符合常规的编程习惯。其实,正确且更常用的方法是使用 for 循环来获取生成器的元素,因为生成器本身也是可迭代对象呀,示例如下:

这样,我们就能轻松地遍历生成器的所有元素了,而且无需操心 StopIteration 错误,for 循环会自动在迭代完所有元素后结束,让代码更加简洁和易于理解。

生成器的功能十分强大,当推算元素的算法比较复杂,使用类似列表生成式那样简单的 for 循环无法实现时,我们还可以借助函数来创建生成器。

就拿著名的斐波那契数列(Fibonacci)来说吧,它的特点是除第一个和第二个数外,任意一个数都可由前两个数相加得到,数列呈现出 1, 1, 2, 3, 5, 8, 13, 21, 34,… 这样的规律。使用列表生成式很难写出生成斐波那契数列的代码,但是用普通函数把它打印出来倒是相对容易,代码如下:

仔细分析这个函数,它实际上定义了斐波那契数列的推算规则,能够从第一个元素开始,推算出后续任意的元素,这种逻辑和生成器的按需生成元素的特性非常相似。

那怎么把这个普通函数变成生成器函数呢?其实只需要把函数中的 print(b) 改为 yield b 就行了,改造后的代码如下:

如此一来,这个函数就不再是普通函数了,而是一个生成器函数。当我们调用一个生成器函数时,它会返回一个生成器对象,示例如下:

输出结果会是 <generator object fib at 0x104feaaa0>,表明得到了一个生成器对象。

这里要重点理解一下生成器函数和普通函数在执行流程上的区别哦。普通函数是顺序执行的,遇到 return 语句或者执行到最后一行函数语句时就返回结果,整个执行过程是一次性的。而生成器函数则不同,它在每次调用 next() 函数的时候才会执行,遇到 yield 语句就会暂停执行并返回 yield 后面的值,下次再调用 next() 函数时,又会从上次返回的 yield 语句处继续往下执行。

我们通过一个简单的例子来进一步说明这个执行流程,定义一个生成器函数,使其依次返回数字 1,3,5,代码如下:

调用这个生成器函数时,首先要生成一个生成器对象,然后用 next() 函数不断获取下一个返回值,执行过程如下:

运行代码后,输出结果如下:

可以清晰地看到,这个 odd 函数作为生成器函数,在执行过程中,每遇到一个 yield 就会中断执行,下次再调用 next() 函数时又继续从上次中断的地方执行。当执行完所有的 yield 语句后,已经没有可执行的 yield 了,所以再调用 next() 函数就会报错。

另外,还有一点需要特别留意哦,调用生成器函数会创建一个生成器对象,多次调用生成器函数则会创建多个相互独立的生成器。有些小伙伴可能会发现这样的情况,每次调用 next(odd()) 好像每次都只返回第一个值,示例如下:

输出结果都是:

这是因为每次调用 odd() 都会创建一个新的生成器对象,上述代码实际上创建了 3 个完全独立的生成器,对这 3 个不同的生成器分别调用 next() 函数,当然每个都会返回各自的第一个值啦。正确的做法应该是先创建一个生成器对象,然后不断对这同一个生成器对象调用 next() 函数,示例如下:

这样就能按照预期依次获取生成器生成的各个元素了。

回到斐波那契数列的 fib 函数例子,在循环过程中,由于有 yield 语句,函数会不断中断执行。当然,我们要给循环设置一个合适的条件来退出循环,不然就会生成一个无限数列了。

同样的,把函数改成生成器函数后,我们在实际应用中基本上很少会用 next() 函数来获取下一个返回值,而是直接使用 for 循环来迭代生成器,示例如下:

这样就能方便地获取斐波那契数列的前 6 个数了。

不过,当使用 for 循环调用生成器时,有一个小细节要注意哦,我们没办法直接拿到生成器函数中 return 语句的返回值。要是想获取这个返回值,就必须捕获 StopIteration 错误,因为生成器函数的返回值包含在 StopIteration 异常的 value 属性中,示例如下:

通过上述代码,我们既能正常迭代生成器获取数列中的元素,又能获取到生成器函数最后的返回值啦。

本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com

点赞 0
收藏 0

文章为作者独立观点不代本网立场,未经允许不得转载。