Der*_*lds 10 python csv iterator
有一个自定义迭代器的问题,它只会迭代文件一次.我seek(0)在迭代之间调用相关的文件对象,但是在第二次运行StopIteration的第一次调用时抛出next().我觉得我忽略了一些显而易见的事情,但我会对此有一些新的看法:
class MappedIterator(object):
"""
Given an iterator of dicts or objects and a attribute mapping dict,
will make the objects accessible via the desired interface.
Currently it will only produce dictionaries with string values. Can be
made to support actual objects later on. Somehow... :D
"""
def __init__(self, obj=None, mapping={}, *args, **kwargs):
self._obj = obj
self._mapping = mapping
self.cnt = 0
def __iter__(self):
return self
def reset(self):
self.cnt = 0
def next(self):
try:
try:
item = self._obj.next()
except AttributeError:
item = self._obj[self.cnt]
# If no mapping is provided, an empty object will be returned.
mapped_obj = {}
for mapped_attr in self._mapping:
attr = mapped_attr.attribute
new_attr = mapped_attr.mapped_name
val = item.get(attr, '')
val = str(val).strip() # get rid of whitespace
# TODO: apply transformers...
# This allows multi attribute mapping or grouping of multiple
# attributes in to one.
try:
mapped_obj[new_attr] += val
except KeyError:
mapped_obj[new_attr] = val
self.cnt += 1
return mapped_obj
except (IndexError, StopIteration):
self.reset()
raise StopIteration
class CSVMapper(MappedIterator):
def __init__(self, reader, mapping={}, *args, **kwargs):
self._reader = reader
self._mapping = mapping
self._file = kwargs.pop('file')
super(CSVMapper, self).__init__(self._reader, self._mapping, *args, **kwargs)
@classmethod
def from_csv(cls, file, mapping, *args, **kwargs):
# TODO: Parse kwargs for various DictReader kwargs.
return cls(reader=DictReader(file), mapping=mapping, file=file)
def __len__(self):
return int(self._reader.line_num)
def reset(self):
if self._file:
self._file.seek(0)
super(CSVMapper, self).reset()
Run Code Online (Sandbox Code Playgroud)
样品用法:
file = open('somefile.csv', 'rb') # say this file has 2 rows + a header row
mapping = MyMappingClass() # this isn't really relevant
reader = CSVMapper.from_csv(file, mapping)
# > 'John'
# > 'Bob'
for r in reader:
print r['name']
# This won't print anything
for r in reader:
print r['name']
Run Code Online (Sandbox Code Playgroud)
我认为你最好不要尝试这样做,.seek(0)而是每次都从文件名中打开文件.
我不建议你只是返回self的__iter__()方法.这意味着您只有一个对象实例.我不知道有人试图从两个不同的线程中使用你的对象的可能性有多大,但如果发生这种情况,结果会令人惊讶.
因此,保存文件名,然后在__iter__()方法中,使用刚刚初始化的读取器对象和新打开的文件句柄对象创建一个新对象; 从中返回这个新对象__iter__().无论文件类对象到底是什么,这都会每次都有效.它可以是从服务器提取数据的网络功能的句柄,或者谁知道什么,它可能不支持.seek()方法; 但是你知道,如果你再次打开它,你将获得一个新的文件句柄对象.如果有人使用该threading模块并行运行您的类的10个实例,则每个实例将始终从文件中获取所有行,而不是每个随机获得大约十分之一的行.
另外,我不建议在.next()方法中使用异常处理程序MappedIterator.该.__iter__()方法应该返回一个可以可靠迭代的对象.如果一个愚蠢的用户传入一个整数对象(例如:3),那么这将是不可迭代的.在里面.__iter__()你总是可以显式地调用iter()一个参数,如果它已经是一个迭代器(例如,一个打开的文件句柄对象),你将获得相同的对象; 但如果它是一个序列对象,你将获得一个对序列起作用的迭代器.现在,如果用户传入3,则调用iter()将引发在用户传递3的行上有意义的异常,而不是来自第一次调用的异常.next().作为奖励,您不再需要cnt成员变量,并且您的代码会更快一些.
所以,如果你把我的所有建议放在一起,你可能会得到这样的结论:
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
Run Code Online (Sandbox Code Playgroud)
现在,.__iter__()每次调用它时,该方法都会为您提供一个新对象.
请注意示例代码如何使用字符串列表而不是打开文件.在此示例中,您需要指定open_with()要使用的函数而不是默认值open()来打开文件.由于我们的字符串列表可以迭代一次返回一个字符串,因此我们可以在这里简单地使用iter我们的open_with函数.
我不明白你的映射代码. csv.reader返回一个字符串值列表,而不是某种字典,所以我写了一些简单的映射代码,适用于有两列的CSV文件,第一列是字符串.显然,你应该删除我的普通映射代码并输入所需的映射代码.
另外,我拿出你的.__len__()方法.当你做类似的事情时,它返回一个序列的长度len(obj); 你让它返回line_num,这意味着len(obj)每次调用.next()方法时,值都会改变.如果用户想要知道长度,他们应该将结果存储在列表中并获取列表的长度,或类似的东西.
编辑:我添加**self._kwargs到调用call_with()的.__iter__()方法.这样,如果你的call_with()函数需要任何额外的参数,它们将被传递.在我做出这个改变之前,没有充分的理由将kwargs参数保存在对象中; 将call_with类参数添加到类.__init__()方法中,默认参数为None.我认为这种改变很好.