当前位置: 首页 > 科技观察

Python编程:如何获取生成器(Generator)和表达式?快来盘吧!

时间:2023-03-22 01:29:45 科技观察

前言在前面的章节中,我们了解了迭代器,这是一个很棒的工具,尤其是当您需要处理大型数据集时。但是,在Python中构建自己的迭代器有点麻烦且耗时。您必须定义一个实现迭代器协议(__iter__()和__next__()方法)的新类。在这个类中,您需要管理变量的内部状态并自行更新它们。另外,当__next__()方法没有返回值时,需要抛出StopIteration异常。有没有更好的方法来实现它?答案是肯定的!这就是Python的生成器(Generator)解决方案。下面我们一起来看看吧。什么是发电机?如果在Python中有一个优雅的解决方案来更有效地构建您自己的迭代器,那就太好了。Python提供的生成器(Generator)就是用来帮助我们轻松创建迭代器的。生成器允许您声明一个行为类似于迭代器的函数,也就是说,它可以在for循环中使用。简单地说,生成器是一个返回迭代器对象的函数。所以这也是创建迭代器的简单方法。创建迭代器时,不需要考虑所有需要的工作(如迭代协议和内部状态等),因为Generator会处理所有这些工作。接下来,让我们更进一步,轻松了解生成器在Python中的工作原理以及如何定义它们。定义生成器如上一节所述,生成器是Python中的一种特殊类型的函数。该函数不返回单个值,而是一个迭代器对象。在生成器函数中,返回值使用yield语句而不是return语句。下面定义了一个简单的生成器函数,代码清单如下:代码清单Fragment-01在上面的清单中,我们定义了一个生成器函数。该函数执行yield语句而不是return关键字。yield语句使这个函数成为生成器。当我们调用这个函数时,它会返回(yield)一个迭代器对象。我们再来看看生成器的调用:代码清单Fragment-02调用生成器通常类似于创建一个对象,调用生成器函数,并将其赋值给一个变量。运行程序的输出如下:YieldingFirstItemAYieldingSecondItemBYieldingLastItemC在应用程序生成器代码中,我们调用了firstGenerator()函数,它是一个生成器,并返回一个迭代器对象。我们将此迭代器命名为myIter。然后对该迭代器对象调用next()函数。在每个next()调用中,迭代器按顺序执行yield语句并返回一个项目。根据规则,这个生成器函数不应该包含return关键字。因为如果是的话,那么return语句就会终止函数,就没有办法满足迭代器的要求了。现在,让我们借助for循环定义一个更实用的生成器。在这个例子中,我们将定义一个生成器,它将跟踪生成从0开始到给定最大限制的数字序列。代码清单如下:CodeListingFragment-03运行程序的输出类似于以下内容:0123在上面的代码清单中,我们定义了一个生成器函数,它生成从0到给定数字的整数。如您所见,yield语句位于for循环内。请注意,n的值会自动存储在连续的next()调用中。需要注意的一点是,在定义生成器时,返回值必须是yield语句,并不是说生成器不能有return语句。只是返回非None值的return语句通常放在生成器的末尾,并在StopIteration异常中添加额外的信息,以便调用者处理它。示例如下:CodeListingSnippet-04以下是无异常处理运行程序的输出:99100Traceback(mostrecentcalllast):File"...",line11,inprint(next(g))StopIteration:不支持生成大于100的数字!如果对程序进行异常捕获处理(try-except),显示的结果会更简洁,自己运行试试。生成器和普通函数如果一个函数至少包含一个yield语句,那么它就是一个生成器函数。如果需要,还可以包含额外的yield或return语句。yield和return关键字都会从函数返回一些东西。return和yield关键字之间的区别对于生成器来说非常重要。return语句完全终止函数,而yield语句暂停函数,保存其所有状态,并在后续调用中恢复执行。我们像调用普通函数一样调用生成器函数。但是在执行过程中,生成器会在遇到yield关键字时暂停。它将迭代器流的当前值发送到调用环境,并等待下一次调用。同时,它在内部保存局部变量及其状态。以下是生成器函数与普通函数不同的关键点:ü生成器函数返回(生成)一个迭代器对象。您无需担心显式创建此迭代器对象,yield关键字会为您完成工作。ü生成器函数必须至少包含一个yield语句。如果需要,它可以包含多个yield关键字。üGenerator函数内部实现了迭代器协议(iter()和next()方法)。üGenerator函数自动保存局部变量及其状态。üGenerator函数在yield关键字处暂停执行,并将控制权交给调用者。ü当迭代器流没有返回值时,Generator函数自动抛出一个StopIteration异常。我们用一个简单的例子来演示普通函数和生成器函数的区别。在此示例中,我们要计算前n个正整数的总和。为此,我们将定义一个函数,该函数给出前n个正数的列表。我们将以两种方式实现这个函数,一个普通函数和一个生成器函数。常用函数代码如下:CodeListingFragment-05程序运行输出类似如下:49999995000000ElapsedTimeinseconds:1.2067763805389404在代码清单中,我们定义了一个常用函数,它返回一个列表的前n个正整数。当我们调用这个函数时,它需要一段时间才能完成执行,因为它创建了一个巨大的列表。它还使用大量内存来完成此任务。现在让我们定义一个生成器函数来实现相同的操作,代码清单如下:代码清单Snippet-06运行程序的结果类似于以下内容:49999995000000(生成器模式)ElapsedTimeinseconds:1.0013225078582764Asseen在生成器列表中,生成器用更少的时间完成同样的任务,使用更少的内存资源。因为生成器一个一个地生成项目而不是返回一个完整的列表。性能提升的主要原因(当我们使用生成器时)是延迟生成值。这种按需值生成减少了内存使用。生成器的另一个优点是你不需要等到所有元素都生成了才开始使用它们。生成器表达式有时我们需要简单的生成器来在我们的代码中执行相对简单的任务。这就是生成器表达式的用武之地。可以使用生成器表达式轻松动态地创建简单的生成器。生成器表达式类似于Python中的lambda函数。但请记住,lambda是匿名函数,它允许我们动态创建单行函数。就像lambda函数一样,生成器表达式创建匿名生成器函数。生成器表达式的语法看起来像列表理解。不同之处在于我们在生成器表达式中使用圆括号而不是方括号。看一下示例:结果如下所示:49999995000000(生成器模式)经过时间(以秒为单位):1.0013225078582764在上面的清单中,我们借助生成器表达式定义了一个简单的生成器。语法如下:cubes_gen=(i**3foriinnums)。您可以在输出中看到生成器对象。正如我们所知,为了能够在生成器中获取项目,我们要么显式调用next()方法,要么使用for循环遍历生成器。接下来打印cubes_gen对象中的items:运行程序,检查遍历的元素items的结果是否和listcomprehension一样。让我们看另一个例子。定义一个将字符串中的字母转换为大写的生成器。然后调用next()方法打印前两个字母。代码示例如下:运行输出如下:MAGeneratorBenefits生成器是很棒的工具,尤其是当你需要在相对有限的内存中处理大数据时。以下是在Python中使用生成器的一些主要好处:1)内存效率:假设您有一个返回非常大的结果序列的普通函数。例如,包含数百万项的列表。您必须等待此函数完成所有执行并将整个列表返回给您。这在时间和内存资源方面显然是低效的。另一方面,如果你使用生成器函数,它会一个一个地返回项目,你将有机会继续下一行代码。而不是等待函数执行列表中的所有项目。因为生成器一次只给你一个项目。2)延迟计算:生成器提供延迟(惰性)计算和评估的功能。惰性评估在实际需要时计算值,而不是在实例化时计算值。假设您有一个大型数据集要计算,惰性计算允许您在整个数据集仍在计算时立即开始处理数据。因为如果使用生成器,则不需要整个数据集。3)易于实现和可读性:生成器非常易于实现并提供良好的代码可读性。请记住,如果您使用生成器,则无需担心__iter__()和__next__()方法。您所需要的只是函数中的一个简单的yield语句。4)处理无限流:当你需要表示无限数据流时,生成器是很好的工具。例如,无限计数器。理论上,你不能在内存中存储无限流,因为你无法确定存储无限流需要多少内存。这是生成器真正发挥作用的地方,因为它一次只生成一个项目,它可以表示无限的数据流。它不需要将所有数据流都存储在内存中。本文总结主要介绍生成器的相关知识,以便更好的自定义迭代器。主题包括什么是生成器?如何自定义生成器以及与普通函数的关键区别?如何实现生成器表达式?并总结生成器的优点。通过本文,相信你可以更轻松高效地掌握Python正则生成器的方方面面。