所有迭代器都缓存吗?csv.Reader呢?
我们知道下面的代码是逐行加载数据,而不是一次性把所有数据都加载到内存中。也就是说,已经读取的行会被操作系统标记为“可删除”。
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.c
中 Reader_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
并不会保留它从迭代器获取的对象,如果没有其他东西在保留它们,那么它们会及时被垃圾回收。
我感觉这个问题背后还有更多的东西你没有告诉我们。你能解释一下为什么你会担心这个吗?