我正在编写一个程序来读取 csv 文件。我已经创建了一个阅读器对象并在它上面调用 next() 给了我标题行。但是当我再次调用它时,虽然 csv 文件中有行,但它给出了 StopIteration 错误。我正在做 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)
请您参考如下方法:
您正在拨打 next每列一次(前两列除外)。因此,如果您有 10 列,它将尝试读取 8 行。
如果您有 20 行,则不会引发异常,但您将忽略最后 12 行,这可能是您不想要的。另一方面,如果您只有 5 行,则在尝试读取第 6 行时它会升高。
原因f.seek(0)防止异常(exception)是它在每个 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而不是普通的
dict , 和
DictReader而不是普通的
reader ——但它已经简单、易读且高效。
