4

我正在编写一个程序来读取 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)
4

2 回答 2

4

next为每一列调用一次(前两列除外)。因此,如果您有 10 列,它将尝试读取 8 行。

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

防止异常的原因f.seek(0)是它将文件重置为 each 之前的开头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))

这仅读取每一行一次,并对每一列执行一些操作,但每行的前两列除外。


从评论中,您真正想要做的是找到每列的最大值。因此,您确实需要对列进行迭代——然后,在每一列中,您需要对行进行迭代。

Acsv.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]))

您可以进一步简化这一点——例如,使用 aCounter代替 plain dict,用 aDictReader代替 plain reader——但它已经很简单、可读且高效。

于 2013-10-06T07:40:15.397 回答
-1

你为什么不写:

header = next(reader)

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

于 2013-10-06T08:00:04.690 回答