读取csv数据时pythoncode中的StopIteration错误

时间:2013-10-06 05:41:01

标签: python csv iterator

大家好我正在编写一个程序来读取csv文件。我已经开始读取一个读取器对象并在其上调用next()给我标题行。但是当我再次调用它时它会给出StopIteration错误,尽管有行csv文件。我正在做file.seek(0)然后它工作正常。任何人请向我解释这个。代码的快照如下:

with open(file,'r') as f:
     reader = csv.reader(f)
     header = next(reader)
     result = []
     for colname in header[2:]:
             col_index = header.index(colname)     
   #          f.seek(0)
             next(reader)

2 个答案:

答案 0 :(得分:2)

您为每列调用next一次(前两列除外)。所以,如果你有10列,它会尝试读8行。

如果您有20行,那么不会引发异常,但您将忽略最后12行,这可能是您不想要的。另一方面,如果你只有5行,那么在尝试阅读第6行时它会提高。

f.seek(0)阻止异常的原因是它将文件重置回每个next之前的开头,因此您只需反复读取标题行,忽略文件中的其他所有内容。它没有提出任何东西,但它没有用处。

你可能想要的是这样的:

with open(file,'r') as f:
    reader = csv.reader(f)
    header = next(reader)
    result = []
    for row in reader:
        for col_index, colname in enumerate(header)[2:]:
            value = row[col_index]
            result.append(do_something_with(value, colname))

这会完全读取每一行,并对每列执行某些操作,但每行的前两行。


从评论中,您实际想要做的是找到每列的最大值。因此,您需要迭代列 - 然后,在每列中,您需要遍历行。

csv.reader是一个迭代器,这意味着你只能迭代一次。所以,如果你只是以明显的方式做到这一点,它将无法运作:

maxes = {}
with open(file) as f:
    reader = csv.reader(f)
    header = next(reader)
    for col_index, colname in enumerate(header)[2:]:
        maxes[colname] = max(reader, key=operator.itemgetter(col_index))

读取标题后,第一列将读取剩下的内容,这很好。下一列将读取整个文件后剩下的内容,这一点都没有。


那么,你怎么解决这个问题?

一种方法是每次通过外部循环重新创建迭代器:

maxes = {}
with open(file) as f:
    reader = csv.reader(f)
    header = next(reader)
for col_index, colname in enumerate(header)[2:]:
    with open(file) as f:
        reader = csv.reader(f)
        next(reader)
        maxes[colname] = max(reader, key=lambda row: float(row[col_index]))

问题在于你正在读取整个文件N次,从磁盘上读取文件可能是程序执行速度最慢的。


您尝试使用f.seek(0)尝试的操作取决于文件对象和csv.reader对象的工作方式。虽然文件对象是迭代器,但它们很特殊,因为它们有一种方法可以将它们重置为开头(或者保存一个位置并稍后返回它)。 csv.reader对象基本上是文件对象周围的简单包装器,因此如果重置文件,还会重置读取器。 (目前尚不清楚这是否有效,但如果你知道csv如何运作,你可能会说服自己在实践中它是安全的。)所以:

maxes = {}
with open(file) as f:
    reader = csv.reader(f)
    header = next(reader)
    for col_index, colname in enumerate(header)[2:]:
        f.seek(0)
        next(reader)
        maxes[colname] = max(reader, key=lambda row: float(row[col_index]))

这样可以节省每次关闭和打开文件的成本,但这不是昂贵的部分;你还在做一遍又一遍的磁盘读取。现在,任何阅读代码的人都必须理解使用文件对象作为迭代器但重置它们的技巧,或者他们不知道代码是如何工作的。


那么,你怎么能避免这种情况呢?

通常,只要需要在迭代器上进行多次传递,就有两种选择。简单的解决方案是将迭代器复制到可重用的迭代中,如列表:

maxes = {}
with open(file) as f:
    reader = csv.reader(f)
    header = next(reader)
    rows = list(reader)
for col_index, colname in enumerate(header)[2:]:
    maxes[colname] = max(rows, key=lambda row: float(row[col_index]))

这不仅比早期代码简单得多,而且速度也快得多。除非文件很大。通过将所有行存储在列表中,您可以立即将整个文件读入内存。如果它太大而不适合,你的程序将会失败。或者更糟糕的是,如果它适合,但只能使用虚拟内存,那么每次进行循环时,程序都会将部分内存与内存进行交换,从而破坏交换文件并使所有内容变得缓慢。


另一种选择是重新组织事情,所以你只需要进行一次通过。这意味着您必须将循环放在外部的行上,并将循环放在内部的列上。它需要重新思考一下设计,这意味着你不能只使用简单的max函数,但权衡可能是值得的:

with open(file) as f:
    reader = csv.reader(f)
    header = next(reader)
    maxes = {colname: float('-inf') for colname in header[2:]}
    for row in reader:
        for col_index, colname in enumerate(header)[2:]:
            maxes[colname] = max(maxes[colname], float(row[col_index]))

您可以进一步简化此操作 - 例如,使用Counter代替普通dictDictReader代替普通reader - 但它已经很简单了,可读,高效。

答案 1 :(得分:-1)

你为什么不写:

header = next(reader)

在最后一行呢?我不知道这是不是你的问题,但我会从那里开始。