您为每列调用一次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 代替普通的dict,使用DictReader 代替普通的reader——但它已经很简单、可读且高效.