python 正确的方法来重置csv.reader的多次迭代?

wpx232ag  于 2023-01-08  发布在  Python
关注(0)|答案(3)|浏览(134)

自定义迭代器有一个问题,因为它只会在文件上迭代一次。我在两次迭代之间对相关的文件对象调用seek(0),但在第二次运行时第一次调用next()时抛出StopIteration。我觉得我忽略了一些明显的东西,但希望对此有一些新的看法:

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()

样品使用:

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']
vptzau2j

vptzau2j1#

我认为您最好不要尝试执行.seek(0),而是每次都从文件名打开文件。
我不建议你只在__iter__()方法中返回self,这意味着你只有一个对象的示例,我不知道有多大可能有人试图从两个不同的线程使用你的对象,但如果发生这种情况,结果将是令人惊讶的。
因此,保存文件名,然后在__iter__()方法中创建一个新对象,该对象包含一个新初始化的读取器对象和一个新打开的文件句柄对象;从__iter__()返回这个新对象。无论类似文件的对象实际上是什么,每次都可以这样做。它可能是从服务器拉取数据的网络函数的句柄,或者谁知道是什么,并且它可能不支持.seek()方法;但是你知道如果你再次打开它,你会得到一个新的文件句柄对象,如果有人用threading模块并行运行你的类的10个示例,每个示例都会得到文件中的所有行,而不是随机得到十分之一的行。
另外,我不推荐在MappedIterator.next()方法中使用异常处理器,.__iter__()方法应该返回一个可以可靠迭代的对象,如果一个愚蠢的用户传入一个整型对象(例如:在.__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

现在,每次调用.__iter__()方法时,它都会为您提供一个新的对象。
请注意示例代码是如何使用字符串列表而不是打开文件的。在本例中,您需要指定一个open_with()函数来代替默认的open()来打开文件。由于字符串列表可以迭代,一次返回一个字符串,因此我们可以简单地使用iter作为open_with函数。
csv.reader返回一个字符串值列表,而不是某种字典,所以我写了一些简单的Map代码,用于两列的CSV文件,第一列是字符串。显然,您应该删除我的简单Map代码,并放入所需的Map代码。
另外,我取出了.__len__()方法,当你执行类似len(obj)的操作时,它返回序列的长度;你让它返回line_num,这意味着每次调用.next()方法,len(obj)的值都会改变,如果用户想知道长度,他们应该把结果存储在一个列表中,然后取列表的长度,或者类似的东西。
编辑:我在.__iter__()方法中添加了**self._kwargscall_with()的调用中。这样,如果你的call_with()函数需要任何额外的参数,它们将被传递。在我做这个修改之前,没有一个真正好的理由将kwargs参数保存在对象中;在类.__init__()方法中添加一个call_with参数,默认参数为None,也是一样好的。

t40tm48m

t40tm48m2#

对于DictReader:

f = open(filename, "rb")
d = csv.DictReader(f, delimiter=",")

f.seek(0)
d.__init__(f, delimiter=",")

对于DictWriter:

f = open(filename, "rb+")
d = csv.DictWriter(f, fieldnames=fields, delimiter=",")

f.seek(0)
f.truncate(0)
d.__init__(f, fieldnames=fields, delimiter=",")
d.writeheader()
f.flush()
h4cxqtbf

h4cxqtbf3#

DictReader对象似乎没有跟随打开文件上的seek()命令,因此next()调用是从文件末尾连续进行的。
在您的reset中,您可以重新打开该文件(您还需要将文件名存储在self._filename中):

def reset(self):
     if self._file:
         self._file.close()
         self._file = open(self._filename, 'rb')

您还可以查看文件对象的子类化,类似于this问题的第一个答案。

相关问题