将时间分割成时段 - 有没有比列表推导更好的方法?
我有一组事件的数据(具体来说是推文),我想把这些数据分成几个小组。下面的代码看起来运行得不错(假设我分成100个小组):
HOUR = timedelta(hours=1)
start = datetime.datetime(2009,01,01)
z = [dt + x*HOUR for x in xrange(1, 100)]
但是,我在Python的文档中看到了一句让我困惑的话:“这使得可以使用 zip(*[iter(s)]*n)
的方式将数据系列分成n个长度的小组。”这个zip的用法确实有效,但我不太明白它是怎么工作的(比如说,*
这个符号到底是什么意思?)。我该如何使用它来让我的代码看起来更好呢?我猜这意味着我应该创建一个生成器/可迭代对象,用来每小时输出一次时间?
2 个回答
文档中的表达式看起来是这样的:
zip(*[iter(s)]*n)
这等同于:
it = iter(s)
zip(*[it, it, ..., it]) # n times
[...]*n
这个部分是把列表重复了 n
次,这样就得到了一个包含 n
个对同一个迭代器的引用的列表。
这又等于:
it = iter(s)
zip(it, it, ..., it) # turning a list into positional parameters
在列表前面的 *
是把列表里的元素变成了函数调用的参数。
现在,当调用 zip 的时候,它会从左到右开始调用这些迭代器,以获取应该被分在一起的元素。因为所有的参数都指向同一个迭代器,所以它会得到初始序列的前 n
个元素。然后这个过程会继续进行,得到结果列表中的第二组元素,依此类推。
最终的结果就和你这样构建列表是一样的(从左到右计算):
it = iter(s)
[(it.next(), it.next(), ..., it.next()), (it.next(), it.next(), ..., it.next()), ...]
我来试着用一个简单的例子来解释一下 zip(*[iter(s)]*n)
。
想象一下,你有一个列表 s = [1, 2, 3, 4, 5, 6]
。
iter(s)
会给你一个 listiterator
对象,每次你想要列表中的一个元素时,它就会返回下一个数字。
[iter(s)] * n
会生成一个列表,其中包含 iter(s)
这个对象 n 次,比如说 [iter(s)] * 2 = [<listiterator object>, <listiterator object>]
- 这里的关键是,这两个其实是指向 同一个 迭代器对象,而不是两个不同的迭代器对象。
zip
函数可以接收多个序列,并返回一个包含元组的列表,每个元组里有来自每个序列的第 i 个元素。例如,zip([1,2], [3,4], [5,6]) = [(1, 3, 5), (2, 4, 6)]
,其中 (1, 3, 5)
是传给 zip
的参数中的第一个元素,(2, 4, 6)
是第二个元素。
在 *[iter(s)]*n
前面的 *
是用来把 [iter(s)]*n
从一个列表转换成多个参数传给 zip
。所以如果 n
是 2,我们就得到了 zip(<listiterator object>, <listiterator object>)
。
zip
会从每个参数中请求下一个元素,但因为这两个都是指向同一个迭代器的引用,所以结果会是 (1, 2)
,接着再请求一次会得到 (3, 4)
,再一次会得到 (5, 6)
,然后就没有更多的元素了,所以它就停止了。因此结果是 [(1, 2), (3, 4), (5, 6)]
。这就是把数据系列聚集成 n 长度的组的过程。