<p>我认为最好不要尝试执行<code>.seek(0)</code>,而是每次都从文件名中打开文件。</p>
<p>我不建议您只返回<code>__iter__()</code>方法中的<code>self</code>。这意味着你的对象只有一个实例。我不知道有人从两个不同的线程尝试使用您的对象的可能性有多大,但如果发生这种情况,结果会令人惊讶。</p>
<p>因此,保存文件名,然后在<code>__iter__()</code>方法中,使用新初始化的读取器对象和新打开的文件句柄对象创建一个新对象;从<code>__iter__()</code>返回这个新对象。这将每次工作,不管什么样的文件对象是真正的。它可以是从服务器中提取数据的网络函数的句柄,或者谁知道是什么,它可能不支持<code>.seek()</code>方法;但是您知道,如果您再次打开它,您将得到一个新的文件句柄对象。如果有人使用<code>threading</code>模块并行运行您的类的10个实例,那么每个实例将始终从文件中获取所有行,而不是每个实例随机获取大约十分之一行。</p>
<p>另外,我不建议在<code>MappedIterator</code>中的<code>.next()</code>方法内使用异常处理程序。<code>.__iter__()</code>方法应该返回一个可以可靠迭代的对象。如果一个愚蠢的用户传入一个整数对象(例如:3),这将是不可iterable的。在<code>.__iter__()</code>中,您始终可以显式地对参数调用<code>iter()</code>,如果它已经是一个迭代器(例如,一个打开的文件句柄对象),您将只得到相同的对象;但是如果它是一个序列对象,您将得到一个对序列起作用的迭代器。现在,如果用户传入3,则对<code>iter()</code>的调用将在用户传入3的行处引发一个有意义的异常,而不是来自对<code>.next()</code>的第一个调用的异常。作为奖励,您不再需要<code>cnt</code>成员变量,您的代码将更快一些。</p>
<p>所以,如果你把我所有的建议放在一起,你可能会得到这样的结果:</p>
<pre><code>class CSVMapper(object):
def __init__(self, reader, fname, mapping={}, **kwargs):
self._reader = reader
self._fname = fname
self._mapping = mapping
self._kwargs = kwargs
self.line_num = 0
def __iter__(self):
cls = type(self)
obj = cls(self._reader, self._fname, self._mapping, **self._kwargs)
if "open_with" in self._kwargs:
open_with = self._kwargs["open_with"]
f = open_with(self._fname, **self._kwargs)
else:
f = open(self._fname, "rt")
# "itr" is my standard abbreviation for an iterator instance
obj.itr = obj._reader(f)
return obj
def next(self):
item = self.itr.next()
self.line_num += 1
# If no mapping is provided, item is returned unchanged.
if not self._mapping:
return item # csv.reader() returns a list of string values
# we have a mapping so make a mapped object
mapped_obj = {}
key, value = item
if key in self._mapping:
return [self._mapping[key], value]
else:
return item
if __name__ == "__main__":
lst_csv = [
"foo, 0",
"one, 1",
"two, 2",
"three, 3",
]
import csv
mapping = {"foo": "bar"}
m = CSVMapper(csv.reader, lst_csv, mapping, open_with=iter)
for item in m: # will print every item
print item
for item in m: # will print every item again
print item
</code></pre>
<p>现在<code>.__iter__()</code>方法在每次调用时都会给您一个新对象。</p>
<p>请注意示例代码如何使用字符串列表而不是打开文件。在本例中,需要指定要使用的<code>open_with()</code>函数,而不是默认的<code>open()</code>函数来打开文件。由于我们的字符串列表可以一次迭代返回一个字符串,因此我们可以在这里简单地使用<code>iter</code>作为<code>open_with</code>函数。</p>
<p>我不明白你的映射代码。<code>csv.reader</code>返回一个字符串值列表,而不是某种字典,因此我编写了一些简单的映射代码,用于CSV文件,其中有两列,第一列是字符串。很明显,您应该切掉我的简单映射代码,并放入所需的映射代码。</p>
<p>另外,我还使用了您的<code>.__len__()</code>方法。当您执行类似于<code>len(obj)</code>的操作时,这将返回序列的长度;您让它返回<code>line_num</code>,这意味着每次调用<code>len(obj)</code>方法时,<code>.next()</code>的值都将更改。如果用户想知道长度,他们应该将结果存储在一个列表中,并获取列表的长度,或者类似的内容。</p>
<p>编辑:我在<code>.__iter__()</code>方法中对<code>call_with()</code>的调用中添加了<code>**self._kwargs</code>。这样,如果您的<code>call_with()</code>函数需要任何额外的参数,它们将被传递。在我进行此更改之前,没有很好的理由将<code>kwargs</code>参数保存在对象中;将<code>call_with</code>参数添加到类<code>.__init__()</code>方法中同样好,默认参数为<code>None</code>。我认为这是一个很好的改变。</p>