4

以下 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. 你不认为这种行为是反直觉的吗?
4

4 回答 4

3

生成器对象一个迭代器,因此是一次性的。它不是一个可以产生任意数量的独立迭代器的迭代器这种行为不是您可以通过某个地方的开关来改变的,因此任何解决方法都相当于使用可迭代的(例如列表)而不是生成器或重复构造生成器。

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

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

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

于 2013-08-28T11:27:59.627 回答
2

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

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

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

于 2013-08-28T11:27:03.457 回答
1

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

于 2013-08-29T11:18:52.973 回答
1

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

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是多态的,具有任何其他可迭代对象,例如 atuplelist

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

于 2013-09-07T15:40:12.993 回答