阅读翻译Fluent Python之An Arithmetic Progression Generator(第17章的一个小节)
关于
- 首次发表日期: 2024-07-16
- Fluent Python亚马逊链接: https://www.amazon.com/Fluent-Python-Concise-Effective-Programming/dp/1492056359
- 使用ChatGPT和KIMI机翻,然后人工润色
- 如有错误,请不吝指出
An Arithmetic Progression Generator(算术级数生成器)
经典的迭代器模式主要用于遍历:浏览某种数据结构。但当项目是即时生成生成时, 基于一个方法(method)去读取系列(series)中下一个项目的标准接口也是非常有用的。例如,内置的 range
函数生成一个有界的算术级数(AP)整数。如果你需要生成任何类型的数字的算术级数,而不仅仅是整数,怎么办?
示例 17-11 展示了一些对 ArithmeticProgression
类的控制台测试,我们稍后会看到该类的实现。示例 17-11 中构造函数的签名是 ArithmeticProgression(begin, step[, end])
。内置 range
函数的完整签名是 range(start, stop[, step])
。我选择实现一个不同的签名,因为在算术级数中,步长是必需的,而终点是可选的。我还将参数名称从 start/stop 改为 begin/end,以明确我选择了不同的签名。在示例 17-11 中的每个测试中,我都对结果调用 list()
以检查生成的值。
注意,根据 Python 算术的数值强制转换规则,生成的算术级数中数字的类型与 begin + step
的类型一致。在示例 17-11 中,你可以看到 int
、float
、Fraction
和 Decimal
类型的列表。示例 17-12 列出了 ArithmeticProgression
类的实现。
__init__
需要两个参数:begin
和step
;end
是可选的,如果它是None
,序列将是无界的。- 获取
self.begin
和self.step
相加的类型。例如,如果一个是int
,另一个是float
,result_type
将是float
。 - 这行代码创建了一个结果,其数值与
self.begin
相同,但被强制转换为后续加法的类型。 - 为了可读性,如果
self.end
属性是None
,forever
标志将为True
,从而产生一个无界序列。 - 这个循环会一直运行,直到结果匹配或超过
self.end
。当这个循环退出时,函数也会退出。 - 当前
result
被产生。 - 计算下一个可能的结果。它可能永远不会被产出,因为
while
循环可能会终止。
在示例17-12的最后一行中,我没有选择每次循环时将self.step
加到前一个结果上,而是选择忽略前一个结果,并通过将self.begin
加上self.step
乘以index
来得到每一个新结果。这样可以避免连续加法后浮点错误累积的影响。下面这些简单的实验清楚地展示了差异:
示例17-12中的ArithmeticProgression
类按预期工作,并且是使用生成器函数实现__iter__
特殊方法的另一个例子。然而,如果一个类的全部目的就是通过实现__iter__
来构建一个生成器,我们可以将这个类替换为一个生成器函数。毕竟,生成器函数就是一个生成器工厂。
示例 17-13 展示了一个名为 aritprog_gen
的生成器函数,它与 ArithmeticProgression
做相同的工作,但代码更少。如果你调用 aritprog_gen
而不是 ArithmeticProgression
,示例 17-11 中的所有测试都能通过。
Example 17-13. The aritprog_gen generator function
def aritprog_gen(begin, step, end=None):
result = type(begin + step)(begin)
forever = end is None
index = 0
while forever or result < end:
yield result
index += 1
result = begin + step * index
示例 17-13 很优雅,但请记住:标准库中有很多现成的生成器可供使用,下一节将展示使用 itertools
模块的更简短的实现。
Arithmetic Progression with itertools (使用 itertools
实现算术级数)
Python 3.10中的itertools
模块有20个生成器函数,它们可以以多种有趣的方式组合使用。
例如,itertools.count
函数返回一个生成器,它产生数字。如果没有参数,它会产生从0开始的一系列整数。但你可以提供可选的起始和步长值,以实现与我们的aritprog_gen
函数类似的结果:
itertools.count
永远不会停止,因此如果你调用 list(count())
,Python 将尝试构建一个列表,它会填满所有曾经制造的内存芯片。际上,你的机器在调用失败之前很久就会变得非常不高兴。
另一方面,还有 itertools.takewhile
函数:它返回一个生成器,消耗另一个生成器,并在给定的判定(predicate)评估为 False
时停止。因此,我们可以结合这两个函数,编写如下代码:
>>> gen = itertools.takewhile(lambda n: n < 3, itertools.count(1, .5))
>>> list(gen)
[1, 1.5, 2.0, 2.5]
利用 takewhile
和 count
,示例 17-14 更加简洁。
示例 17-14. aritprog_v3.py:像之前的 aritprog_gen
函数一样工作
import itertools
def aritprog_gen(begin, step, end=None):
first = type(begin + step)(begin)
ap_gen = itertools.count(first, step)
if end is None:
return ap_gen
return itertools.takewhile(lambda n: n < end, ap_gen)
请注意,在示例17-14中的aritprog_gen
不是一个生成器函数:它的主体中没有yield
。但它返回一个生成器,就像生成器函数所做的那样。
然而,回想一下itertools.count
是重复添加步长的,所以它产生的浮点数序列不如示例17-13那样精确。
示例17-14的要点是:在实现生成器时,要了解标准库中已有的内容,否则你很有可能会重新发明轮子。这就是为什么下一节将介绍几个现成的生成器函数。