python生成器的不一致行为

2024-03-29 11:19:19 发布

您现在位置:Python中文网/ 问答频道 /正文

下面的python代码生成[(0,0),(0,7)…(0,693)],而不是组合所有3的倍数和7的倍数的预期元组列表:

multiples_of_3 = (i*3 for i in range(100))
multiples_of_7 = (i*7 for i in range(100))
list((i,j) for i in multiples_of_3 for j in multiples_of_7)

此代码修复了此问题:

^{pr2}$

问题:

  1. 生成器对象似乎扮演着迭代器的角色,而不是每次枚举生成的列表时都提供迭代器对象。后一种策略似乎被.NETLINQ查询对象采用。有没有一种优雅的方法来避开这个问题?在
  2. 第二段代码是怎么工作的?我能理解生成器的迭代器在循环7的所有倍数后没有复位吗?在
  3. 你不认为这种行为是违反直觉的吗?在

Tags: of对象代码in角色列表forrange
3条回答

如果要将生成器表达式转换为多路径iterable,那么可以以相当常规的方式完成。例如:

class MultiPass(object):
    def __init__(self, initfunc):
        self.initfunc = initfunc
    def __iter__(self):
        return self.initfunc()

multiples_of_3 = MultiPass(lambda: (i*3 for i in range(20)))
multiples_of_7 = MultiPass(lambda: (i*7 for i in range(20)))
print list((i,j) for i in multiples_of_3 for j in multiples_of_7)

从定义工作量的角度来看:

^{pr2}$

但是从用户的角度来看,他们写的是multiples_of_3,而不是{},这意味着对象{}与任何其他iterable都是多态的,比如tuple或{}。在

需要输入lambda:有点不雅观,真的。我认为,在保持向后兼容性的同时,向语言引入“可理解的内容”并不会有任何危害。但是标点符号只有这么多,我怀疑这是否值得一个。在

正如您所发现的,由生成器表达式创建的对象是一个迭代器(更确切地说是生成器迭代器),设计为只使用一次。如果需要可重置的生成器,只需创建一个实际的生成器并在循环中使用它:

def multiples_of_3():               # generator
    for i in range(100):
       yield i * 3
def multiples_of_7():               # generator
    for i in range(100):
       yield i * 7
list((i,j) for i in multiples_of_3() for j in multiples_of_7())

第二段代码可以工作,因为内部循环((i*7 ...))的表达式列表在外循环的每一次传递时都会求值。这会导致每次都创建一个新的生成器迭代器,这将提供所需的行为,但以牺牲代码的清晰度为代价。在

要理解发生了什么,请记住,当for循环遍历迭代器时,不会对它进行“重置”。(这是一个特性;这样的重置会使在大型迭代器上的迭代中断成碎片,并且生成器不可能这样做。)例如:

^{pr2}$

……与此相反:

^{3}$

生成器表达式与调用的生成器等效,因此只能迭代一次。在

生成器对象是迭代器,因此是一个快照。它不是一个可以产生任意数量独立迭代器的iterable。这种行为不是你可以用开关改变的,所以任何解决方法都等于使用iterable(例如列表)而不是生成器,或者反复构造生成器。在

第二个片段执行后者。它在定义上等同于循环

for i in (i*3 for i in range(100)):
    for j in (i*7 for i in range(100)):
        ...

希望这并不奇怪,在这里,后一个生成器表达式在外循环的每次迭代中都会重新求值。在

相关问题 更多 >