2017-02-15 5 views
2

私は以下のジレンマを持っています。理論的に無効なレコードを含むことができる巨大なCSVファイルを解析しています(python)。問題をすばやく修正できるように、エラーメッセージの行番号を確認したいと思います。しかし、私は多くのファイルを解析しているため、エラーは非常にまれですが、私はメインパイプラインにオーバーヘッドを追加するエラー処理をしたくありません。だから私はenumerateまたは同様のアプローチを使用したくないのです。一言で言えばPythonでオーバーヘッドのない意味のあるIOエラーメッセージ

、私はこのように動作するget_line_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()will not workとして、複雑しているようです。

EDIT:

オーバーヘッドが非常に重要であるように思えます。私の実際の世界のケースでは(ファイルが非常に短いレコードのリストであるため、単一の浮動小数点数、浮動小数点数の組または文字列と整数の組であるため、file.csvは約800MBで約80M行です)、それは約2.5です1ファイルあたりの秒数はenumerateです。何らかの理由により、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] 

EDIT 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)) 

出力

[244.9097429071553, 242.84596176538616, 242.74369075801224 
[293.32093235617504, 274.17732743313536, 274.00854821596295] 

だから、私の場合には、enumerateからのオーバーヘッドは10%程度です。

+2

だから、あなたの例があまりにも遅く動くか、あまりにもゆっくりと動くかもしれないと思うのが問題ですか? perfに実際にどれだけの影響がありますか?エラーがないことが分かっているファイルの違いを測定しましたか? –

+0

うわー、2倍の減速は期待できませんでした。 'try/catch'の' process(line) '呼び出しをラップするとどれだけの影響がありますか? –

+0

私はどちらかというと、実際にデータと何をしようとしていても「合格」を置き換えることは、公平な比較ではありません。また、csvファイルの1行あたり10バイトしかないのはかなり珍しいことです。 – nigel222

答えて

1

デバッグ情報を追加するのがあまりにもオーバーヘッドであることがわかっている場合(私はそのトピックについて議論したくありません)、この2つのバージョンの関数を実装できます。徹底的なチェックと冗長なデバッグを備えた高性能の1つ。

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

エラーが発生すると処理が再開されるという欠点があります。しかし、手動でエラーを修正しなければならない場合、このような場合に浪費される数秒のペナルティは害を与えません。また、func_verbose()は最初のエラーで停止する必要はなく、ファイル全体をチェックしてすべてのエラーをリストすることができます。

2

は、それは重大なオーバーヘッドを追加していないenumerate

for line_ref, line in enumerate(f): 
    line_no = line_ref + 1 # enumerate starts at zero 

を使用しています。ファイルからレコードを取り出す作業は、カウンタを保持する作業を大幅に超え、forステートメントのタプル代入は単なる名前バインディングであり、参照先のデータの余分なコピーではありません。

置換えの更新:

テストファイルの生成に間違いをしました。今すぐ質問に追加された最初のタイミングテストを確認しました。

個人的には、10バイトのレコードを含む最悪(大文字小文字)ファイルの10%のオーバーヘッドを完全に許容できるものと見なします。

+0

OPは' enumerate'を使いたくないと言いました。あなたが何かを使って回答を投稿しようとしたら、彼は使いたくないと言ったが、少なくともあなたはなぜそれを使いたいのか説明できる。 –

+0

ただ気づいた。私は私の答えを更新しました。 – nigel222

+0

1からのカウントを開始するには、 'enumerate(f、1)'を使います。 – VPfB

0

標準ライブラリ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) 

私は関連する例外、おそらくカスタム例外をキャッチすることをお勧めします。そうしないと予期しない例外がマスクされます。

関連する問題