在Python中,如何对可迭代对象进行多次迭代?

3

我遇到了一些从Dynamo数据库中获取迭代对象的代码,并且我可以这样做:

print [en["student_id"] for en in enrollments]

然而,当我再次进行类似的操作时:

print [en["course_id"] for en in enrollments]

然后第二次迭代将不会输出任何内容,因为迭代结构只能被迭代一次并且已经到达了其结尾。
问题是,如何多次迭代它?对于情况(1),如果我们知道迭代中只有几个项目,怎么办?对于情况(2),如果我们知道迭代中将有大量项目(例如一百万个项目),而且我们不想花费太多额外的内存空间,该怎么办?
相关的是,我查了一下 rewind,似乎它存在于 PHP 和 Ruby 中,但在 Python 中不存在?

除了将所有数据存储在一个变量中之外,另一个选择是使用a, b = itertools.tee(it),但前提是您不需要使用所有或大部分数据与第一个迭代器一起使用。如果是这种情况,最好使用列表。 - Padraic Cunningham
2个回答

8

enrollments是一个生成器。如果需要再次迭代,请重新创建生成器,或者先将其转换为列表:

enrollments = list(enrollments)

请注意,API通常使用生成器来避免内存膨胀;列表必须引用其包含的所有对象,因此所有这些对象必须同时存在。生成器可以按需逐个生成元素;您的列表推导式一旦提取了“student_id”键,就会再次丢弃这些对象。
另一种选择是仅迭代一次,并对每个要执行的对象进行所有操作。因此,不要运行两个列表推导式,而是运行一个常规的for循环,并在一个地方提取您需要的所有数据,同时将其附加到单独的列表中。
courses = []
students = []
for enrollment in enrollments:
    courses.append(enrollment['course_id'])
    students.append(enrollment['student_id'])

在PHP中,rewind与此无关;而Python中有fileobj.seek(0)来实现相同的功能,但文件对象不是生成器。

所以,我从Dynamo数据库调用中获得了数据... 我如何重新创建它呢?(最好不要再次对数据库进行调用,因为这涉及网络和数据库访问) - nonopolarity
2
@太極者無極而生:通過再次調用該函數。 - Martijn Pieters
有趣的是,如果我们从 Dynamo DB(类似于 MongoDB)中获取所有数据,难道不会占用内存中的全部空间吗?这不像我们正在迭代 12 个骰子的所有排列组合,所以我们实际上并不需要存储 6**12 个元组,这种情况下使用生成器将节省大量内存。 - nonopolarity
我不知道DynamoDB如何流式传输结果。我想象缓冲区(磁盘、网络等)会参与其中,不需要一次性将所有内容读入内存。 - Martijn Pieters
有趣...所以如果有30,000 * 5 = 150,000个报名记录(假设有30,000个学生,每个学生平均选修5门课程),使用list(enrollments)可能会有问题。但是如果通过使用studentID获取enrollments,那么通常它只包含5个项目,使用list(enrollments)就没问题了。 - nonopolarity
这取决于情况。如果这是一个网络服务器,还有多少其他的请求在同时执行相同的操作?这些对象有多大?需要考虑到,内存分配也需要时间,所以如果一次性创建150k个对象比较耗时,而通过流式创建对象并可以重复利用内存会更快一些。 - Martijn Pieters

1

注意:与仅使用list(enrollments)相比,这种方法在时间和空间上都不够高效。唯一适用的情况是您希望同时迭代。例如 it1, it2 = tee(iterator, n=2); next(it1); for a,b in zip(it1, it2): # do stuff。在这种情况下,每次迭代只会保留两个值在内存中。然而,如果您首先遍历it1,那么生成的所有值都将存储在一个链表中,这与仅调用list(iterator)相当(实际上效率更低,如前所述)。 - Bakuriu
1
如果你在开始b之前就耗尽了a,请不要使用itertools.tee()。在这种情况下,只需使用list(it)即可。只有在混合迭代teed输出时才使用tee(),以最小化它需要创建的缓冲区。 - Martijn Pieters

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接