繁体   English   中英

python生成器的行为不一致

[英]Inconsistent behavior of python generators

下面的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)

此代码修复了此问题:

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

问题:

  1. 生成器对象似乎扮演迭代器的角色,而不是每次枚举生成的列表时都提供迭代器对象。 后来的策略似乎被.Net LINQ查询对象采用。 有一种优雅的方式来解决这个问题吗?
  2. 为什么第二段代码有效? 我是否理解在循环遍历所有7的倍数后,发生器的迭代器不会被重置?
  3. 难道你不认为这种行为是反直觉的,如果不是不一致的话?

生成器对象迭代器,因此是一次性的。 这不是能产生任何数量的独立迭代的迭代 这种行为不是你可以用某个开关改变的东西,所以任何解决方法相当于使用可迭代(例如列表)而不是生成器或重复构建生成器。

第二个片段是后者。 根据定义,它等同于循环

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

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

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

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循环迭代时,迭代器没有“重置”。 (这是一个特性;这样的重置会破坏迭代的大型迭代器,对于生成器来说是不可能的。)例如:

multiples_of_2 = iter(xrange(0, 100, 2))  # iterator
for i in multiples_of_2:
    print i
# prints nothing because the iterator is spent
for i in multiples_of_2:
    print i

......而不是这个:

multiples_of_2 = xrange(0, 100, 2)        # iterable sequence, converted to iterator
for i in multiples_of_2:
    print i
# prints again because a new iterator gets created
for i in multiples_of_2:
    print i

生成器表达式等同于被调用的生成器,因此只能迭代一次。

如果要将生成器表达式转换为多遍迭代,则可以以相当常规的方式完成。 例如:

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)

从定义事物的角度来看,输入的工作量相似:

def multiples_of_3():
    return (i*3 for i in range(20))

但是从用户的角度来看,他们编写multiples_of_3而不是multiples_of_3() ,这意味着对象multiples_of_3与任何其他可迭代对象是多态的,例如tuplelist

键入lambda:的需要lambda:有点不优雅,真实。 我不认为在语言中引入“可迭代的理解”会给你提供你想要的东西,同时保持向后兼容性。 但是只有那么多标点字符,我怀疑这会被认为是值得的。

我发现的真正问题是关于单通道和多通道迭代,以及目前没有标准机制来确定可迭代的单通道还是多通道的事实:请参阅单通道和多通道迭代

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM