1

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

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

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

这是一个由 3 个可迭代对象组成的可迭代对象,它们本身由数字 1 的 5 次出现组成。然而,这不是发生的情况。我得到的是(翻译成列表)是:

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

也就是说,最里面的迭代没有被复制(看起来),而是一次又一次地使用相同的迭代,导致它的元素用完。

使用地图的一个版本是:

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 中这一事实有关repeat,但这应该有所作为吗?据我所知,lambda x: itr.repeat(1,5)和之间的唯一区别itr.repeat(1,5)是第一个接受参数(然后丢弃),而另一个没有。

4

5 回答 5

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]]
于 2013-09-23T19:42:14.137 回答
2

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

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

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

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

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

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

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], [], []]
于 2013-09-23T20:13:19.037 回答
2

你重复一个迭代器三次。第一次之后,它已经用尽了,所以第二次和第三次迭代它什么都不做;它已经结束了。

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

于 2013-09-23T19:42:14.457 回答
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
于 2013-09-23T19:42:38.177 回答
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多次调用,那么您有多个独立的生成器。

于 2013-09-24T05:34:58.160 回答