所有迭代器都缓存吗?csv.Reader呢?

2 投票
1 回答
1068 浏览
提问于 2025-04-16 20:39

我们知道下面的代码是逐行加载数据,而不是一次性把所有数据都加载到内存中。也就是说,已经读取的行会被操作系统标记为“可删除”。

def fileGen( file ):
    for line in file:
        yield line

with open("somefile") as file:
    for line in fileGen( file ):
        print line

但是如果我们把fileGen的定义改成下面这样,有没有办法验证这个说法是否仍然成立呢?

def fileGen( file ):
    for line in csv.Reader( file ):
        yield line

我们怎么知道csv.Reader会不会缓存它加载的数据呢?谢谢。

祝好,
约翰

1 个回答

4

想要了解 csv.reader 是怎么工作的,最靠谱的方法就是直接看看它的源代码。你可以查看 _csv.c 文件,从第773行开始。你会发现,reader 对象有一个指向底层迭代器的指针(通常是文件迭代器),每当它需要读取下一行时,就会调用 PyIter_Next。所以它不会提前读取数据,也不会缓存已经加载的数据。

还有一种方法可以了解 csv.reader 的工作原理,就是创建一个模拟的文件对象,这个对象可以报告什么时候被查询。例如:

class MockFile:
    def __init__(self): self.line = 0
    def __iter__(self): return self
    def next(self):
        self.line += 1
        print "MockFile line", self.line
        return "line,{0}".format(self.line)

>>> r = csv.reader(MockFile())
>>> next(r)
MockFile line 1
['line', '1']
>>> next(r)
MockFile line 2
['line', '2']

这验证了我们从 csv 源代码中学到的内容:它只在自己的 next 方法被调用时,才会向底层迭代器请求下一行。


约翰明确表示(见评论),他关心的是 csv.reader 是否会保留这些行,防止它们被 Python 的内存管理器回收。

你可以选择阅读代码(这是最可靠的)或者做个实验。如果你查看 _csv.cReader_iternext 的实现,你会看到 lineobj 是底层迭代器返回的对象的名称,并且在代码的每个路径上都有调用 Py_DECREF(lineobj)。所以 csv.reader 并不会保留 lineobj

这里有一个实验可以确认这一点。

class FinalizableString(string):
    """A string that reports its deletion."""
    def __init__(self, s): self.s = s
    def __str__(self): return self.s
    def __del__(self): print "*** Deleting", self.s

class MockFile:
    def __init__(self): self.line = 0
    def __iter__(self): return self
    def next(self):
        self.line += 1
        return FinalizableString("line,{0}".format(self.line))

>>> r = csv.reader(MockFile())
>>> next(r)
*** Deleting line,1
['line', '1']
>>> next(r)
*** Deleting line,2
['line', '2']

所以你可以看到 csv.reader 并不会保留它从迭代器获取的对象,如果没有其他东西在保留它们,那么它们会及时被垃圾回收。


我感觉这个问题背后还有更多的东西你没有告诉我们。你能解释一下为什么你会担心这个吗?

撰写回答