将时间分割成时段 - 有没有比列表推导更好的方法?

5 投票
2 回答
627 浏览
提问于 2025-04-15 23:49

我有一组事件的数据(具体来说是推文),我想把这些数据分成几个小组。下面的代码看起来运行得不错(假设我分成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 个回答

5

文档中的表达式看起来是这样的:

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()),  ...]
5

我来试着用一个简单的例子来解释一下 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 长度的组的过程。

撰写回答