流畅的 Python 第二版(GPT 重译)(九)(1)https://developer.aliyun.com/article/1484704
提示
我发现在谈论从生成器获得的值时严谨是有帮助的。说生成器“返回”值是令人困惑的。函数返回值。调用生成器函数返回一个生成器。生成器产生值。生成器不以通常的方式“返回”值:生成器函数体中的return
语句会导致生成器对象引发StopIteration
。如果在生成器中return x
,调用者可以从StopIteration
异常中检索到x
的值,但通常使用yield from
语法会自动完成,我们将在“从协程返回值”中看到。
示例 17-7 使for
循环和函数体之间的交互更加明确。
示例 17-7. 一个在运行时打印消息的生成器函数
>>> def gen_AB(): ... print('start') ... yield 'A' # ① ... print('continue') ... yield 'B' # ② ... print('end.') # ③ ... >>> for c in gen_AB(): # ④ ... print('-->', c) # ⑤ ... start # ⑥ --> A # ⑦ continue # ⑧ --> B # ⑨ end. # ⑩ >>> ⑪
①
在for
循环中对④的第一次隐式调用next()
将打印'start'
并在第一个yield
处停止,产生值'A'
。
②
for
循环中第二次隐式调用next()
将打印'continue'
并在第二个yield
处停止,产生值'B'
。
③
第三次调用next()
将打印'end.'
并穿过函数体的末尾,导致生成器对象引发StopIteration
。
④
为了迭代,for
机制执行等效于g = iter(gen_AB())
以获取一个生成器对象,然后在每次迭代时执行next(g)
。
⑤
循环打印-->
和next(g)
返回的值。这个输出只会在生成器函数内部的print
调用输出之后出现。
⑥
文本start
来自生成器体中的print('start')
。
⑦
生成器体中的yield 'A'
产生值A,被for
循环消耗,赋给变量c
,导致输出--> A
。
⑧
迭代继续,第二次调用next(g)
,将生成器体从yield 'A'
推进到yield 'B'
。第二个print
在生成器体中输出continue
。
⑨
yield 'B'
产生值B,被for
循环消耗,赋给循环变量c
,因此循环打印--> B
。
⑩
迭代继续,第三次调用next(it)
,推进到函数体的末尾。由于生成器体中的第三个print
,输出中出现了end.
。
⑪
当生成器函数运行到末尾时,生成器对象会引发StopIteration
异常。for
循环机制捕获该异常,循环干净地终止。
现在希望清楚了示例 17-5 中的Sentence.__iter__
是如何工作的:__iter__
是一个生成器函数,当调用时,会构建一个实现Iterator
接口的生成器对象,因此不再需要SentenceIterator
类。
第二个Sentence
版本比第一个更简洁,但不像它可以那样懒惰。如今,懒惰被认为是一个好特性,至少在编程语言和 API 中是这样。懒惰的实现将产生值推迟到最后可能的时刻。这样可以节省内存,也可能避免浪费 CPU 周期。
我们将构建懒惰的Sentence
类。
懒惰的句子
Sentence
的最终变体是懒惰的,利用了re
模块中的懒惰函数。
句子第四次尝试:懒惰生成器
Iterator
接口被设计为懒惰的:next(my_iterator)
每次产生一个项目。懒惰的相反是急切:懒惰评估和急切评估是编程语言理论中的技术术语。
到目前为止,我们的Sentence
实现并不懒惰,因为__init__
急切地构建了文本中所有单词的列表,并将其绑定到self.words
属性。这需要处理整个文本,而且列表可能使用的内存和文本本身一样多(可能更多;这取决于文本中有多少非单词字符)。如果用户只迭代前几个单词,大部分工作将是徒劳的。如果你想知道,“在 Python 中有没有一种懒惰的方法?”答案通常是“是的”。
re.finditer
函数是re.findall
的惰性版本。re.finditer
返回一个生成器,按需产生re.MatchObject
实例,而不是一个列表。如果有很多匹配,re.finditer
可以节省大量内存。使用它,我们的第三个Sentence
版本现在是惰性的:只有在需要时才从文本中读取下一个单词。代码在示例 17-8 中。
示例 17-8. sentence_gen2.py: 使用调用re.finditer
生成器函数实现的Sentence
import re import reprlib RE_WORD = re.compile(r'\w+') class Sentence: def __init__(self, text): self.text = text # ① def __repr__(self): return f'Sentence({reprlib.repr(self.text)})' def __iter__(self): for match in RE_WORD.finditer(self.text): # ② yield match.group() # ③
①
不需要有一个words
列表。
②
finditer
在self.text
上的RE_WORD
匹配中构建一个迭代器,产生MatchObject
实例。
③
match.group()
从MatchObject
实例中提取匹配的文本。
生成器是一个很好的快捷方式,但可以用生成器表达式进一步简化代码。
第五种句子:惰性生成器表达式
我们可以用生成器表达式替换前一个Sentence
类中的简单生成器函数(示例 17-8)。就像列表推导式构建列表一样,生成器表达式构建生成器对象。示例 17-9 对比了它们的行为。
示例 17-9. gen_AB
生成器函数被列表推导式使用,然后被生成器表达式使用
>>> def gen_AB(): # ① ... print('start') ... yield 'A' ... print('continue') ... yield 'B' ... print('end.') ... >>> res1 = [x*3 for x in gen_AB()] # ② start continue end. >>> for i in res1: # ③ ... print('-->', i) ... --> AAA --> BBB >>> res2 = (x*3 for x in gen_AB()) # ④ >>> res2 <generator object <genexpr> at 0x10063c240> >>> for i in res2: # ⑤ ... print('-->', i) ... start # ⑥ --> AAA continue --> BBB end.
①
这是与示例 17-7 中相同的gen_AB
函数。
②
列表推导式急切地迭代由gen_AB()
返回的生成器对象产生的项目:'A'
和'B'
。注意下面行中的输出:start
,continue
,end.
③
这个for
循环迭代由列表推导式构建的res1
列表。
④
生成器表达式返回res2
,一个生成器对象。这里生成器没有被消耗。
⑤
只有当for
循环迭代res2
时,这个生成器才从gen_AB
获取项目。for
循环的每次迭代隐式调用next(res2)
,进而调用gen_AB()
返回的生成器对象上的next()
,将其推进到下一个yield
。
⑥
注意gen_AB()
的输出如何与for
循环中的print
输出交错。
我们可以使用生成器表达式进一步减少Sentence
类中的代码量。参见示例 17-10。
示例 17-10. sentence_genexp.py: 使用生成器表达式实现的Sentence
import re import reprlib RE_WORD = re.compile(r'\w+') class Sentence: def __init__(self, text): self.text = text def __repr__(self): return f'Sentence({reprlib.repr(self.text)})' def __iter__(self): return (match.group() for match in RE_WORD.finditer(self.text))
与示例 17-8 唯一的区别是__iter__
方法,在这里不是一个生成器函数(没有yield
),而是使用生成器表达式构建一个生成器,然后返回它。最终结果是一样的:__iter__
的调用者得到一个生成器对象。
生成器表达式是一种语法糖:它们总是可以被生成器函数替代,但有时更加方便。下一节将介绍生成器表达式的用法。
何时使用生成器表达式
在实现示例 12-16 中的Vector
类时,我使用了几个生成器表达式。这些方法中的每一个都有一个生成器表达式:__eq__
、__hash__
、__abs__
、angle
、angles
、format
、__add__
和__mul__
。在所有这些方法中,列表推导也可以工作,但会使用更多内存来存储中间列表值。
在示例 17-10 中,我们看到生成器表达式是一种创建生成器的语法快捷方式,而无需定义和调用函数。另一方面,生成器函数更加灵活:我们可以使用多个语句编写复杂逻辑,甚至可以将它们用作协程,正如我们将在“经典协程”中看到的那样。
对于更简单的情况,一目了然的生成器表达式更易于阅读,就像Vector
示例所示。
我在选择要使用的语法时的经验法则很简单:如果生成器表达式跨越多行,我更倾向于出于可读性考虑编写生成器函数。
语法提示
当将生成器表达式作为函数或构造函数的单个参数传递时,您无需为函数调用编写一组括号,然后再为生成器表达式加上另一组括号。只需一对即可,就像在示例 12-16 中Vector
调用__mul__
方法时一样,如下所示:
def __mul__(self, scalar): if isinstance(scalar, numbers.Real): return Vector(n * scalar for n in self) else: return NotImplemented
但是,如果在生成器表达式之后还有更多的函数参数,您需要将其括在括号中,以避免SyntaxError
。
我们看到的Sentence
示例演示了生成器扮演经典迭代器模式的角色:从集合中检索项。但是,我们也可以使用生成器产生独立于数据源的值。下一节将展示一个示例。
但首先,让我们简要讨论迭代器和生成器之间重叠概念。
算术级数生成器
经典的迭代器模式完全关乎遍历:导航某些数据结构。但是,基于一种方法来获取系列中的下一个项的标准接口在项是实时生成的情况下也很有用,而不是从集合中检索。例如,range
内置函数生成整数的有界算术级数(AP)。如果您需要生成任何类型的数字的算术级数,而不仅仅是整数,该怎么办?
示例 17-11 展示了我们马上将看到的ArithmeticProgression
类的一些控制台测试。示例 17-11 中构造函数的签名是ArithmeticProgression(begin, step[, end])
。range
内置函数的完整签名是range(start, stop[, step])
。我选择实现不同的签名,因为在算术级数中step
是必需的,但end
是可选的。我还将参数名称从start/stop
更改为begin/end
,以明确表明我选择了不同的签名。在示例 17-11 的每个测试中,我对结果调用list()
以检查生成的值。
示例 17-11。ArithmeticProgression
类演示
>>> ap = ArithmeticProgression(0, 1, 3) >>> list(ap) [0, 1, 2] >>> ap = ArithmeticProgression(1, .5, 3) >>> list(ap) [1.0, 1.5, 2.0, 2.5] >>> ap = ArithmeticProgression(0, 1/3, 1) >>> list(ap) [0.0, 0.3333333333333333, 0.6666666666666666] >>> from fractions import Fraction >>> ap = ArithmeticProgression(0, Fraction(1, 3), 1) >>> list(ap) [Fraction(0, 1), Fraction(1, 3), Fraction(2, 3)] >>> from decimal import Decimal >>> ap = ArithmeticProgression(0, Decimal('.1'), .3) >>> list(ap) [Decimal('0'), Decimal('0.1'), Decimal('0.2')]
请注意,生成的算术级数中的数字类型遵循 Python 算术的数字强制转换规则,即begin + step
的类型。在示例 17-11 中,您会看到int
、float
、Fraction
和Decimal
数字的列表。示例 17-12 列出了ArithmeticProgression
类的实现。
示例 17-12。ArithmeticProgression
类
class ArithmeticProgression: def __init__(self, begin, step, end=None): # ① self.begin = begin self.step = step self.end = end # None -> "infinite" series def __iter__(self): result_type = type(self.begin + self.step) # ② result = result_type(self.begin) # ③ forever = self.end is None # ④ index = 0 while forever or result < self.end: # ⑤ yield result # ⑥ index += 1 result = self.begin + self.step * index # ⑦
①
__init__
需要两个参数:begin
和step
;如果end
是None
,则序列将是无界的。
②
获取self.begin
和self.step
的添加类型。例如,如果一个是int
,另一个是float
,result_type
将是float
。
③
这一行创建了一个result
,其数值与self.begin
相同,但被强制转换为后续加法的类型。⁷
④
为了可读性,如果self.end
属性为None
,forever
标志将为True
,导致一个无界系列。
⑤
这个循环运行forever
,或直到结果匹配或超过self.end
。当这个循环退出时,函数也会退出。
⑥
当前的result
被生成。
⑦
下一个潜在的结果被计算。它可能永远不会被产生,因为while
循环可能终止。
在示例 17-12 的最后一行,我选择忽略每次循环中将self.step
添加到前一个result
中,而是选择忽略前一个result
,并通过将self.begin
添加到self.step
乘以index
来添加每个新的result
。这避免了连续添加后浮点错误的累积效应。这些简单的实验使差异变得明显:
>>> 100 * 1.1 110.00000000000001 >>> sum(1.1 for _ in range(100)) 109.99999999999982 >>> 1000 * 1.1 1100.0 >>> sum(1.1 for _ in range(1000)) 1100.0000000000086
来自示例 17-12 的ArithmeticProgression
类按预期工作,并且是使用生成器函数实现__iter__
特殊方法的另一个示例。然而,如果一个类的整个目的是通过实现__iter__
来构建一个生成器,我们可以用生成器函数替换类。毕竟,生成器函数本质上是一个生成器工厂。
示例 17-13 展示了一个名为aritprog_gen
的生成器函数,它与ArithmeticProgression
执行相同的工作,但代码更少。如果只调用aritprog_gen
而不是ArithmeticProgression
,则示例 17-11 中的所有测试都会通过。⁸
示例 17-13. aritprog_gen
生成器函数
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
模块的更短实现。
使用itertools
的算术进度
Python 3.10 中的itertools
模块有 20 个生成器函数,可以以各种有趣的方式组合。
例如,itertools.count
函数返回一个生成器,产生数字。没有参数时,它产生以0
开头的一系列整数。但是你可以提供可选的start
和step
值来实现类似于我们的aritprog_gen
函数的结果:
>>> import itertools >>> gen = itertools.count(1, .5) >>> next(gen) 1 >>> next(gen) 1.5 >>> next(gen) 2.0 >>> next(gen) 2.5
警告
itertools.count
永远不会停止,因此如果调用list(count())
,Python 将尝试构建一个填满所有已制造的内存芯片的list
。实际上,在调用失败之前,您的机器会变得非常不高兴。
另一方面,有itertools.takewhile
函数:它返回一个消耗另一个生成器并在给定谓词评估为False
时停止的生成器。因此,我们可以将两者结合起来写成这样:
>>> gen = itertools.takewhile(lambda n: n < 3, itertools.count(1, .5)) >>> list(gen) [1, 1.5, 2.0, 2.5]
利用takehwhile
和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
会重复添加step
,因此它生成的浮点数序列不像示例 17-13 那样精确。
示例 17-14 的要点是:在实现生成器时,要了解标准库中提供了什么,否则很可能会重复造轮子。这就是为什么下一节涵盖了几个可直接使用的生成器函数。
标准库中的生成器函数
标准库提供了许多生成器,从提供逐行迭代的纯文本文件对象,到令人惊叹的os.walk
函数,该函数在遍历目录树时产生文件名,使递归文件系统搜索就像一个for
循环一样简单。
os.walk
生成器函数令人印象深刻,但在本节中,我想专注于以任意可迭代对象作为参数并返回生成器的通用函数,这些生成器产生选定的、计算的或重新排列的项目。在下面的表格中,我总结了两打这样的函数,来自内置的itertools
和functools
模块。为方便起见,我根据高级功能对它们进行了分组,而不管它们在哪里定义。
第一组包含过滤生成器函数:它们产生输入可迭代对象生成的项目子集,而不改变项目本身。像takewhile
一样,表 17-1 中列出的大多数函数都接受一个predicate
,这是一个一参数布尔函数,将应用于输入中的每个项目,以确定是否将项目包含在输出中。
表 17-1. 过滤生成器函数
模块 | 函数 | 描述 |
itertools |
compress(it, selector_it) |
并行消耗两个可迭代对象;每当selector_it 中对应的项目为真时,从it 中产生项目 |
itertools |
dropwhile(predicate, it) |
消耗it ,跳过predicate 计算为真时的项目,然后产生所有剩余项目(不再进行进一步检查) |
(内置) | filter(predicate, it) |
对iterable 的每个项目应用predicate ,如果predicate(item) 为真,则产生该项目;如果predicate 为None ,则只产生真值项目 |
itertools |
filterfalse(predicate, it) |
与filter 相同,但predicate 逻辑取反:每当predicate 计算为假时产生项目 |
itertools |
islice(it, stop) or islice(it, start, stop, step=1) |
从it 的切片中产生项目,类似于s[:stop] 或s[start:stop:step] ,除了it 可以是任何可迭代对象,且操作是惰性的 |
itertools |
takewhile(predicate, it) |
当predicate 计算为真时产生项目,然后停止,不再进行进一步检查 |
示例 17-15 中的控制台列表显示了表 17-1 中所有函数的使用。
示例 17-15. 过滤生成器函数示例
>>> def vowel(c): ... return c.lower() in 'aeiou' ... >>> list(filter(vowel, 'Aardvark')) ['A', 'a', 'a'] >>> import itertools >>> list(itertools.filterfalse(vowel, 'Aardvark')) ['r', 'd', 'v', 'r', 'k'] >>> list(itertools.dropwhile(vowel, 'Aardvark')) ['r', 'd', 'v', 'a', 'r', 'k'] >>> list(itertools.takewhile(vowel, 'Aardvark')) ['A', 'a'] >>> list(itertools.compress('Aardvark', (1, 0, 1, 1, 0, 1))) ['A', 'r', 'd', 'a'] >>> list(itertools.islice('Aardvark', 4)) ['A', 'a', 'r', 'd'] >>> list(itertools.islice('Aardvark', 4, 7)) ['v', 'a', 'r'] >>> list(itertools.islice('Aardvark', 1, 7, 2)) ['a', 'd', 'a']
下一组包含映射生成器:它们产生从输入可迭代对象的每个单独项目计算得到的项目,或者在map
和starmap
的情况下,产生自输入可迭代对象的项目。表 17-2 中的生成器每个输入可迭代对象产生一个结果。如果输入来自多个可迭代对象,则一旦第一个输入可迭代对象耗尽,输出就会停止。
表 17-2. 映射生成器函数
模块 | 函数 | 描述 |
itertools |
accumulate(it, [func]) |
产生累积和;如果提供了func ,则产生将其应用于第一对项目的结果,然后应用于第一个结果和下一个项目等的结果 |
(内置) | enumerate(iterable, start=0) |
产生形式为(index, item) 的 2 元组,其中index 从start 计数,item 取自iterable |
(内置) | map(func, it1, [it2, …, itN]) |
将func 应用于it 的每个项目,产生结果;如果给出了 N 个可迭代对象,则func 必须接受 N 个参数,并且可迭代对象将并行消耗 |
itertools |
starmap(func, it) |
将func 应用于it 的每个项目,产生结果;输入可迭代对象应产生可迭代对象iit ,并且func 被应用为func(*iit) |
Example 17-16 演示了itertools.accumulate
的一些用法。
示例 17-16。itertools.accumulate
生成器函数示例
>>> sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1] >>> import itertools >>> list(itertools.accumulate(sample)) # ① [5, 9, 11, 19, 26, 32, 35, 35, 44, 45] >>> list(itertools.accumulate(sample, min)) # ② [5, 4, 2, 2, 2, 2, 2, 0, 0, 0] >>> list(itertools.accumulate(sample, max)) # ③ [5, 5, 5, 8, 8, 8, 8, 8, 9, 9] >>> import operator >>> list(itertools.accumulate(sample, operator.mul)) # ④ [5, 20, 40, 320, 2240, 13440, 40320, 0, 0, 0] >>> list(itertools.accumulate(range(1, 11), operator.mul)) [1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800] # ⑤
①
运行总和。
②
运行最小值。
③
运行最大值。
④
运行乘积。
⑤
从1!
到10!
的阶乘。
Table 17-2 的其余函数显示在 Example 17-17 中。
示例 17-17。映射生成器函数示例
>>> list(enumerate('albatroz', 1)) # ① [(1, 'a'), (2, 'l'), (3, 'b'), (4, 'a'), (5, 't'), (6, 'r'), (7, 'o'), (8, 'z')] >>> import operator >>> list(map(operator.mul, range(11), range(11))) # ② [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100] >>> list(map(operator.mul, range(11), [2, 4, 8])) # ③ [0, 4, 16] >>> list(map(lambda a, b: (a, b), range(11), [2, 4, 8])) # ④ [(0, 2), (1, 4), (2, 8)] >>> import itertools >>> list(itertools.starmap(operator.mul, enumerate('albatroz', 1))) # ⑤ ['a', 'll', 'bbb', 'aaaa', 'ttttt', 'rrrrrr', 'ooooooo', 'zzzzzzzz'] >>> sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1] >>> list(itertools.starmap(lambda a, b: b / a, ... enumerate(itertools.accumulate(sample), 1))) # ⑥ [5.0, 4.5, 3.6666666666666665, 4.75, 5.2, 5.333333333333333, 5.0, 4.375, 4.888888888888889, 4.5]
①
从1
开始对单词中的字母编号。
②
从0
到10
的整数的平方。
③
并行从两个可迭代对象中相乘的数字:当最短的可迭对象结束时,结果停止。
④
这就是zip
内置函数的作用。
⑤
根据单词中的位置重复每个字母,从1
开始。
⑥
运行平均值。
接下来,我们有合并生成器组 - 所有这些都从多个输入可迭代对象中产生项目。chain
和chain.from_iterable
按顺序消耗输入可迭代对象(一个接一个地),而product
、zip
和zip_longest
并行消耗输入可迭代对象。参见 Table 17-3。
Table 17-3。合并多个输入可迭代对象的生成器函数
模块 | 函数 | 描述 |
itertools |
chain(it1, …, itN) |
从it1 ,然后从it2 等无缝地产生所有项目 |
itertools |
chain.from_iterable(it) |
从it 生成的每个可迭代对象中产生所有项目,一个接一个地无缝地;it 将是一个可迭代对象,其中项目也是可迭代对象,例如,元组列表 |
itertools |
product(it1, …, itN, repeat=1) |
笛卡尔积:通过组合来自每个输入可迭代对象的项目生成 N 元组,就像嵌套的for 循环可以产生的那样;repeat 允许多次消耗输入可迭代对象 |
(内置) | zip(it1, …, itN, strict=False) |
从并行获取的每个项目构建 N 元组,默默地在第一个可迭代对象耗尽时停止,除非给出strict=True ^(a) |
itertools |
zip_longest(it1, …, itN, fillvalue=None) |
从并行获取的每个项目构建 N 元组,仅在最后一个可迭代对象耗尽时停止,用fillvalue 填充空白 |
^(a) strict 关键字参数是 Python 3.10 中的新参数。当strict=True 时,如果任何可迭代对象的长度不同,则会引发ValueError 。默认值为False ,以确保向后兼容性。 |
示例 17-18 展示了itertools.chain
和zip
生成器函数及其相关函数的使用。请记住,zip
函数是以拉链拉链(与压缩无关)命名的。zip
和itertools.zip_longest
都是在“神奇的 zip”中引入的。
示例 17-18. 合并生成器函数示例
>>> list(itertools.chain('ABC', range(2))) # ① ['A', 'B', 'C', 0, 1] >>> list(itertools.chain(enumerate('ABC'))) # ② [(0, 'A'), (1, 'B'), (2, 'C')] >>> list(itertools.chain.from_iterable(enumerate('ABC'))) # ③ [0, 'A', 1, 'B', 2, 'C'] >>> list(zip('ABC', range(5), [10, 20, 30, 40])) # ④ [('A', 0, 10), ('B', 1, 20), ('C', 2, 30)] >>> list(itertools.zip_longest('ABC', range(5))) # ⑤ [('A', 0), ('B', 1), ('C', 2), (None, 3), (None, 4)] >>> list(itertools.zip_longest('ABC', range(5), fillvalue='?')) # ⑥ [('A', 0), ('B', 1), ('C', 2), ('?', 3), ('?', 4)]
①
通常使用两个或更多可迭代对象调用chain
。
②
当使用单个可迭代对象调用chain
时,它不会产生任何有用的效果。
③
但是chain.from_iterable
从可迭代对象中获取每个项目,并按顺序链接它们,只要每个项目本身是可迭代的。
④
zip
可以并行消耗任意数量的可迭代对象,但是生成器总是在第一个可迭代对象结束时停止。在 Python ≥ 3.10 中,如果给定strict=True
参数并且一个可迭代对象在其他可迭代对象之前结束,则会引发ValueError
。
⑤
itertools.zip_longest
的工作原理类似于zip
,只是它会消耗所有输入的可迭代对象,根据需要用None
填充输出元组。
⑥
fillvalue
关键字参数指定自定义填充值。
itertools.product
生成器是计算笛卡尔积的一种懒惰方式,我们在“笛卡尔积”中使用了多个for
子句的列表推导式构建。具有多个for
子句的生成器表达式也可以用于懒惰地生成笛卡尔积。示例 17-19 演示了itertools.product
。
流畅的 Python 第二版(GPT 重译)(九)(3)https://developer.aliyun.com/article/1484707