Python中无开销的有意义IO错误消息

Python中无开销的有意义IO错误消息,python,performance,io,Python,Performance,Io,我面临以下困境。我正在用python解析巨大的CSV文件,理论上可能包含无效记录。为了能够快速修复问题,我希望看到错误消息中的行号。但是,由于我正在解析许多文件,而且错误非常罕见,我不希望我的错误处理将开销添加到主管道中。这就是为什么我不想使用枚举或类似的方法 简而言之,我正在寻找一个get\u line\u number函数,它的工作方式如下: with open('file.csv', 'r') as f: for line in f: try:

我面临以下困境。我正在用
python
解析巨大的CSV文件,理论上可能包含无效记录。为了能够快速修复问题,我希望看到错误消息中的行号。但是,由于我正在解析许多文件,而且错误非常罕见,我不希望我的错误处理将开销添加到主管道中。这就是为什么我不想使用
枚举
或类似的方法

简而言之,我正在寻找一个
get\u line\u number
函数,它的工作方式如下:

with open('file.csv', 'r') as f:
    for line in f:
        try:
            process(line)
        except:
            line_no = get_line_number(f)
            raise RuntimeError('Error while processing the line ' + line_no)
然而,这似乎很复杂,因为在这个循环中
f.tell()

编辑:

似乎日常开支相当可观。在我的现实世界中(这很痛苦,因为文件是非常短的记录列表:单浮点数、整数浮点数对或字符串整数对;
file.csv
大约有800MB大,大约有80M行),对于
enumerate
,读取每个文件大约需要2.5秒。由于某些原因,
fileinput
速度非常慢

import timeit
s = """
with open('file.csv', 'r') as f:
    for line in f:
        pass
"""
print(timeit.repeat(s, number = 10, repeat = 3))
s = """
with open('file.csv', 'r') as f:
    for idx, line in enumerate(f):
        pass
"""
print(timeit.repeat(s, number = 10, repeat = 3))
s = """
count = 0
with open('file.csv', 'r') as f:
    for line in f:
        count += 1
"""
print(timeit.repeat(s, number = 10, repeat = 3))
setup = """
import fileinput
"""
s = """
for line in fileinput.input('file.csv'):
    pass
"""
print(timeit.repeat(s, setup = setup, number = 10, repeat = 3))
输出

[45.790788270998746, 44.88589363079518, 44.93949336092919]
[70.25306860171258, 70.28569177398458, 70.2074502906762]
[75.43606997421011, 74.39759518811479, 75.02027251804247]
[325.1898657102138, 321.0400970801711, 326.23809849238023]
[244.9097429071553, 242.84596176538616, 242.74369075801224
[293.32093235617504, 274.17732743313536, 274.00854821596295]
编辑2:

接近真实世界的场景。
try-except
子句位于循环之外,以减少开销

import timeit
setup = """
def process(line):
    if float(line) < 0.5:
        outliers += 1
"""
s = """
outliers = 0
with open('file.csv', 'r') as f:
    for line in f:
        process(line)
"""
print(timeit.repeat(s, setup = setup, number = 10, repeat = 3))
s = """
outliers = 0
with open('file.csv', 'r') as f:
    try:
        for idx, line in enumerate(f):
            process(line)
    except ValueError:
        raise RuntimeError('Invalid value in line' + (idx + 1)) from None
"""
print(timeit.repeat(s, setup = setup, number = 10, repeat = 3))

因此,在我的例子中,
enumerate
的开销约为10%。

请使用
enumerate

for line_ref, line in enumerate(f):
    line_no = line_ref + 1  # enumerate starts at zero
这并没有增加任何显著的开销。从文件中取出记录所涉及的工作远远超过了保存计数器所涉及的工作,for语句中的元组赋值只是一个名称绑定,而不是
line

替换更新:

生成测试文件时出错。现在已经基本上证实了问题中增加的第一个计时测试


就个人而言,我认为10字节记录的最坏(ish)情况文件的10%开销是完全可以接受的,因为替代方案是不知道8000万条记录中的哪一条是错误的

标准库
fileinput
模块内存有效地处理大型文件,并提供内置行号计数器。它还自动为要从命令行参数读取的文件选择多个文件名。然而,似乎没有一种(简单的?)方法将其与上下文处理程序一起使用

至于性能,您需要将其与其他方法进行比较测试

import fileinput

for line in fileinput.input():
    try:
        process(line)
    except:
        line_no = fileinput.filelineno()
        raise RuntimeError('Error while processing the line ' + line_no)

顺便说一句,我建议只捕获相关的异常,可能是自定义的异常,否则您将掩盖意外的异常。

如果您确定添加调试信息的开销太大(我不想就此话题进行争论),您可以实现该函数的两个版本。高性能一对一,具有彻底的检查和详细的调试。基本思想是:

try:
    func_quick(args)
except Exception:
    func_verbose(args)

缺点是当发生错误时,处理将再次开始。但是,如果您必须手动更正错误,在这种情况下浪费几秒钟的惩罚应该不会造成伤害。此外,func_verbose()不必在出现第一个错误时停止,它可能会检查整个文件并列出所有错误。

因此,我不得不问,问题是您的示例运行得太慢,还是您认为它可能运行得太慢?它实际上对性能有多大影响?你有没有在一个你知道没有错误的文件上测量过差异?哇,没想到会有2倍的速度减慢。包装您的
流程(行)
调用
try/catch
有多大影响?我也不会,但用
pass
代替真正对数据做的任何事情并不是最公平的比较。同样,csv文件每行只有10个字节是非常不寻常的。@nigel222:你的平台是什么?你可以在
try/除了
中运行整个循环,以减少开销。OP说他不想使用
枚举
。如果你打算用他说他不想用的东西发布答案,你至少可以解释他为什么要用它。使用
枚举(f,1)
从1开始计数。