ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python编程:如何搞定生成器(Generator)及表达式?来盘它

Python编程:如何搞定生成器(Generator)及表达式?来盘它 紧接着我们要再往前迈出一大步, 目的是要把生成器这个东西究竟是怎么运作的, 还有咱们应当怎样去定义它, 这些都清清楚楚地弄明白。[]如上所说, 生成器是其中一种类型特别的函数, 这个函数不返回单个数值, 不过它返回一个迭代对象的实例, 在生成器的函数体里面, 返回值这里采用的方式是那个yield的那个语句, 并非是一般意义上的语句, 以下就定义一个简易的生成器的功能函数, 具体的代码展现为下面的清单格式。这是代码清单当中的一部分片段内容, 编号为01。咱们在那份清单里面, 定下了一个作为生成器的函数来用。这个函数在执行的时候, 用的不是那个关键字, 而是用了yield语句。就是因为有了这个yield语句的存在, 所以, 它这才变成了一个生成器。当我们将这个函数给进行调用的时候, 它就会把一个迭代器的对象给回来给我们去使用。接下来, 咱们再回头去看一下, 关于生成器是怎么被调用出来的。代码清单的片段, 是编号为02的那一部分。所谓调用生成器, 通常情况下是跟创建对象有着相似的性质, 具体操作就是去调用那个生成器函数, 然后再把它赋给一个变量。当程序被启动并运行之后, 它所输出的结果表现如下。First ItemItemLast Item[]根据规则可知, 这个生成器函数里面是不应当包含关键字的。为啥? 因为如果它万一碰巧包含了这些关键字的话, 语句就会把此函数给终止掉。这样一来, 也就没法去满足迭代器的那个要求了。眼下, 我们得依靠 for 循环来出手, 定义出一个更有实际使用价值的生成器。具体到这种例子里面, 我们要去建立一个生成器, 这个生成器会一直追踪、一直往下出列那种从数字零开始往后面数的序列数, 一直到达到设定的最大限值是为止。代码清单, 以下就是。这是一个关于代码清单的片段展示, 其编号标记为零三。运行该程序以后, 所得到的输出结果情况与如下的展示方式比较相像。在上面的那个列表里面, 我们定义了一个叫生成器的函数, 这个函数它是用来生成那些整数的, 也就是从零开始一直到你给我的那个数字为止。就像大家看到的那样, yield这个语句是放在一个for循环里面的。然后, 请大家务必注意一下, 变量n的值会按照顺序自动地存储起来, 每次去调用的时候就调用next()这个方法。有一点需要特别注意一下, 在定义一个生成器的时候, 其核心功能是通过yield语句来体现的, 这不是说生成器里面就不能有别的语句存在, 通常情况下, 会有一个返回非None值的语句被放置在生成器的末尾位置处, 这样做的目的是为了给可能出现异常的情况添加一些额外提示信息, 这样一来调用者就可以更好地处理这些异常情况了, 下面是具体的示例展示。这是代码清单里头的片段部分, 编号为04。当程序在运行过程中没有进行异常处理的时候, 得到的输出结果可能类似于如下所示的情况。99100大多数人在最后一次尝试时都会失败。文件在……, 第11行, 在这个地方。print(next(g))目前这个平台是不支持来生成那些大于100的大数字的, 所以用户需要注意一下。如果针对程序异常进行了捕捉处理操作, 那么最终的输出显示会变得更为清晰简洁, 建议大家亲自动手运行程序进行实验验证。生成器这个物件, 还有普通的函数这两个概念。如果一个函数当中至少包含了一个用于表示生成器含义的yield语句, 那么这个函数就会被认定为是一个生成器函数。在存在这种需求的情形之下, 该函数内部同样可以被安排有其他类型的yield内容或者是常规的逻辑语句。当进行执行操作时, yield关键词以及相关的内容结构均具备从所属函数的内部向外部传递并输出一些具体结果数据的能力与功能。和yield关键字之间的差异对于生成器来说非常重要。语句会完全终止函数。而yield语句会暂停函数。它会保存它的所有状态。然后在后续的调用中, 它会继续执行。我们在使用生成器函数的时候, 方法和调用普通函数的操作是一样的。不过, 在运行的过程中, 当代码运行到出现yield这个关键字的地方时, 生成器就会暂停下来。在这个时候, 它会把当前处于迭代流中的那个值发给正在调用的环境去使用。然后, 它就会开始等待, 直到下一次被再次调用为止。与此同时, 它还会在自己的内部把这个过程的局部变量还有它们的状态都保存好。这里列出来了一些关键点, 这些关键点把生成器函数和普通函数给区分开了, 让大家能够看清楚两者的不同之处。这个函数会返回一个用来进行迭代的数据对象。你完全不需要去担心必须亲自显式地把这个数据对象给创建出来, 因为有一个叫做关键字的东西叫作yield。正是这个关键字替你完成了该对象的创建工作。我们用一个简单的例子来演示普通函数和生成器函数之间的区别, 在这个例子中, 我们要计算前n个正整数的和, 为此, 我们将定义一个函数, 该函数给出前n个正数的列表, 我们将以两种方式实现这个函数, 一个是普通函数, 一个是生成器函数。普通函数代码如下这个片段是属于代码清单中的第05部分。运行程序输出结果类似如下Time in : 1.29404在代码清单这里, 我们要定义一个普普通通的函数, 这个函数的作用就是去返回前n个正整数的那些数字所组成的一个列表, 等到我们真正把这个函数拿去调用的时候它必须要花掉一段不短的时间才能把执行动作给完成掉, 这是因为它在干活的过程中所创建出来的那个列表实在是太过庞大了, 大到让人有点喘不过气来, 而且, 为了把这件事情给办成, 它同时也消耗掉了非常多的内存资源。现在的话, 就让我们来给那个相同的操作去定义一个生成器的函数, 好让它可以被实现出来, 具体的那个代码清单呢, 就是下面这个样子的:这个部分展示的是代码清单片段, 具体内容编号为06。运行程序结果类似如下这是生成器模式, 里面的时间数值是1.82764。就像我们在生成器清单里能看到的那样, 生成器能够在更短的时间内完成同样的任务, 并且消耗更少的内存资源。这是因为它是一个一个地产生项目, 而并不是返回完整的列表。我们要进行性能改进, 主要原因是什么呢? 原因在于值的惰性生成。具体点说, 就是当我们使用生成器的时候。所谓按需生成值, 这种方式是有明显效果的。它的效果就是会降低内存使用量。这还只是其中一个优点。另一个优点更值得注意。你根本不需要等待所有元素都生成完毕。你可以在需要的时刻直接使用它们。生成器表达式在现实情况里, 咱们确实时常得靠一些简简单单的生成器去做代码里头那种比较普通的活儿。这玩意儿就是生成器表达式该大展身手的地方了。咱们要是想动手变出一堆简单点的生成器出来, 直接用生成器表达式去动态搞一搞就行了, 挺方便的。生成器表达式类似于普通函数, 但是我们要记住的是匿名这个性质, 它允许我们动态地创建只有一行的函数内容。就像普通的函数一样, 生成器表达式所创建的其实也是匿名性质的生成器函数这一概念。那个生成器表达式的语法看着就像是一个列表推导式, 可不同的地方在于, 咱们在用那个生成器表达式的时候, 会把原本在列表推导式里会用到的那些方括号给换掉, 然后换成圆括号来用, 具体是个啥样子的情况, 大家可以瞧一下底下这个示例。最终的运行表现大致就像是下面这个样子。在上面那个清单里面呢, 我们借助生成器表达式帮忙, 定义了一个挺简单的生成器。下面的内容是关于语法的信息: 等于号后面跟着左括号, 然后是i立方, 接着是for循环针对nums, 最后用右括号闭合, 也就是(i3 for i in nums)。你可以在输出结果那里看到生成器对象。正如同大家已经知道的那样, 为了能够在这个生成器里去拿到里面的项, 咱们要么选择显式调用next()这种方法, 要么使用for循环来把生成器进行遍历。接下来的内容就关于打印对象中所包含的项了, 具体的情况如下进行展示:当你去执行那个程序的以后, 你所看到的那些被遍历出来的元素项的最终结果, 是否真的会和由列表推导式所生成的结果保持一致呢。我们再看看另外一个例子, 具体就是创建一个生成器函数, 让这个函数的功能是把字符串当中的每一个字母都变成大写形式, 接下来再去调用那个名为 next 的方法并且输出最前面的两个字符对应的内容, 相关的代码样本展示在下面这个位置。程序的运行结果输出在了屏幕上面, 具体情况如下所示:生成器好处生成器是极为出色的工具, 尤其在需要于相对有限的内存空间内处理庞大体积的数据之时, 其优势便凸显出来。下面将列举在应用过程中使用生成器所涉及的一些核心益处。1内存效率:咱们假如遇到了一个普通函数, 这个函数的返回值会是一个特别大的序列。举个例子来说, 这个序列可能包含数百万项的数据。要是你调用这个函数, 你就得硬着头皮等它把所有的代码都执行完, 然后才能把整个列表交到你手里。在时间和内存这两种资源的使用效率上面来看, 这种做法显然太低效了。换个角度讲, 如果你改用生成器函数来处理问题, 那么它就会一项接一项地把结果返回给你。这样的话, 你就有机会去继续执行后面的代码行。你根本不需要干巴巴地等着函数把那个列表里的所有项全部处理完毕, 这省了很多事。因为生成器一次性只给你一项。2延迟计算:生成器这个工具, 它提供了一个可以延迟进行计算的求值功能。这个延迟计算是个啥意思? 也就是说, 它在真正需要那个值的时候, 才去计算那个值, 而不是在一开始实例化的时候就把值给算出来。假设你手头有一个特别大的数据集, 需要去计算它里面的东西, 那么, 如果你用延迟计算的方法的话, 它就允许你在整个数据集还在一步步计算生成的过程中, 你就可以立即开始使用那些已经出来的数据了。这是为啥, 这是因为如果使用生成器的话, 你就根本不需要等到整个数据集全部计算完, 也就不存在需要整个数据集的情况。3实现起来比较方便, 而且阅读和理解起来也容易。生成器的实现过程是非常轻松的, 而且它能够提供非常良好的代码可读性大家需要记住的是, 一旦选择使用生成器这一技术手段, 你就完全不需要去担心有关那两个括号的特定方法该如何处理, 在函数内部仅仅安排一个非常基础的生成关键字的使用语句, 这就足够满足所有需求了。4对无限流这一题材进行处理。当你遇到需要表现那种无限流淌下去的数据序列这种状况时, 生成器就变成了一种特别有用的好工具。比如, 我们可以构建出一个永远都在增加数字的无限计数器。从理论层面来说, 你根本没法在计算机内存当中把这样无限的流程全部存下来, 原因是你完全搞不清楚存储这么大没边界的流程究竟会需要消耗掉多大规模的内存空间。这正是生成器真正能够大展拳脚的关键所在的地方, 因为它每次只负责制造并吐出那唯一的、单独的一项内容。所以它完全有能力去代表那些无穷无尽的数据流, 而且这个过程里头, 它绝对不会把所有的所有数据流一股脑儿全都堆积塞进内存里面去。本文小结主要内容是介绍了生成器相关的知识, 目的是为了能够更好的自定义迭代器。文中包括了以下几个问题。一个是关于什么叫做生成器。第二个是如何去自定义生成器, 以及它和普通函数的关键区别是什么。第三个是为了如何实现生成器的表达式。最后还总结了生成器的优点。相信通过阅读这篇文章, 你会更轻松且更高效地掌握常规生成器方面的内容。
返回列表