使用python itertools生成可迭代的迭代。 (使用重复功能)

时间:2013-09-23 19:37:16

标签: python functional-programming itertools

在python中尝试函数式编程时,我注意到两个表达式之间存在差异,我认为应该有相同的结果。

特别是我想要的是拥有一个可迭代的东西(或者我应该说是收益?)其他可迭代的东西。我想做的一个简单例子可能是:

import itertools as itr
itr.repeat(itr.repeat(1,5),3)

这是一个由3个迭代组成的迭代,它们本身由数字1的5个密码组成。然而,这不是发生的事情。我得到的(翻译成列表)是:

[[1,1,1,1,1],[],[]]

也就是说,不会复制最里面的可迭代(看起来如此),而是反复使用相同的iterable,导致元素耗尽。

使用地图有效的版本是:

import itertools as itr
map(lambda x: itr.repeat(1,5), range(3))

这会产生我期望的结果:

[[1,1,1,1,1],[1,1,1,1,1],[1,1,1,1,1]]

我不明白为什么这样可行,而仅使用重复的方法则不然。也许它与以下事实有关:在地图版本中,来自repeat的可迭代包装在一个lambda中,但这应该有所不同吗?据我所知,lambda x: itr.repeat(1,5)itr.repeat(1,5)之间的唯一区别是第一个接受一个参数(然后抛出),而另一个则没有。

5 个答案:

答案 0 :(得分:3)

如您所述,itertools.repeat()不会复制重复的项目,而是每次都使用相同的可迭代项。

map()方法有效,因为lambda函数是为range(3)中的每个元素单独调用的,因此您可以获得三个单独的itertools.repeat(1, 5)个迭代来生成嵌套内容。

要使用itertools完全执行此操作,您可以使用itertools.tee

import itertools as itr
itr.tee(itr.repeat(1, 5), 3)

以下是将结果显示为列表的示例:

>>> [list(x) for x in itr.tee(itr.repeat(1, 5), 3)]
[[1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1]]

答案 1 :(得分:2)

你重复了三次迭代器。在第一次之后,它已经筋疲力尽,所以第二次和第三次迭代都没有做任何事情;它已经结束了。

使用map()创建三个独立的迭代器对象(通过调用lambda),因此不会发生这种情况。

答案 2 :(得分:2)

itertools库生成生成器,生成器只能通过迭代。然后他们只是筋疲力尽,不会再次产生他们的价值观了:

>>> import itertools as itr
>>> repeater = itr.repeat(1, 5)
>>> list(repeater)
[1, 1, 1, 1, 1]
>>> list(repeater)
[]

另一方面,map()版本会生成生成器对象。您也可以使用列表推导:

[itr.repeat(1, 5) for _ in range(3)]

现在该列表中的每个对象都是单独的生成器对象,可以独立迭代。您可以测试对象是否不同:

>>> repeaters = map(lambda x: itr.repeat(1,5), range(3))
>>> for pair in itr.combinations(repeaters, 2):
...     print id(pair[0]), id(pair[1]), 'pair[0] is pair[1]', pair[0] is pair[1]
... 
4557097936 4557097808 pair[0] is pair[1] False
4557097936 4557105040 pair[0] is pair[1] False
4557097808 4557105040 pair[0] is pair[1] False

答案 3 :(得分:2)

不同之处在于itertools.repeat对象作为其第一个参数,并且在迭代时,它会多次生成相同的对象。在这种情况下,该对象只能在耗尽之前迭代一次,因此您看到了结果。

map可调用对象作为其第一个参数,并且每次都会调用该对象,每次都会产生结果。

因此,在您的第一个代码段中,只有一个对象生成1 5次。在你的第二个片段中,有一个lambda对象,但每次调用它时都会创建一个生成1 5次的新生成器对象。

为了得到你想要的东西你通常会写:

(itr.repeat(1,5) for _ in range(3))

获得多个1 5次生成器,或者:

itr.repeat(tuple(itr.repeat(1,5)),3)

因为与itr.repeat的返回不同,元组可以重复迭代。

当然,由于这个例子很小,你可以忘记生成器,然后写:

((1,)*5,)*3

简洁但有点模糊。

您的问题类似于以下内容之间的区别:

# there is only one inner list
foo = [[]] * 3
foo[0].append(0)
foo
# [[0], [0], [0]]

# there are three separate inner lists
bar = [[] for _ in range(3)]
bar[0].append(0)
bar
# [[0], [], []]

答案 4 :(得分:0)

你的直觉是正确的,问题是repeat给你一个生成器,它不断产生相同的对象,而不是对象的副本。生成器对象只能迭代一次;每次迭代中的下一个项目都被生成时,它将永久地从生成器中丢弃。

lambda x: itr.repeat(1,5)itr.repeat(1,5)之间的差异是代码数据之间的差异。当你通过"裸露" repeat调用它已经执行并返回一个生成器对象,它是传递的生成器对象;当你传递lambda然后itr.repeat(1,5)代码在一个尚未执行的函数中,并且它是传递的函数。当lambda被称为然后时,repeat调用被评估并返回一个生成器,这发生每个时间调用lambda,所以你得到一个新的生成器每一次。

由于map为集合的每个元素调用其参数函数而不是一次调用它来获取一个对象然后每次使用该对象,因此您将获得单独的独立生成器对象。由于repeat只重复生成您最初给出的对象,因此您可以获得对单个生成器对象的多个引用。

这与这两个片段之间的区别基本相同:

a = itr.repeat(1, 5)
b = itr.repeat(1, 5)

a = itr.repeat(1, 5)
b = a

如果你调用repeat一次然后传递生成的对象,那么只有一个生成器,并且从所有这些地方都可以看到你从它传递过的任何地方消耗它。如果您多次致电repeat,那么您将拥有多个独立的发电机。