2017-09-25 8 views
2

Python3でファイルを反復処理するときにファイルカーソルの位置を知るにはどうすればよいですか?Python3でファイルの行を繰り返し処理しているときに `tell()`を実行する代わりに?

Python 2.7でそれは簡単です、tell()を使用してください。 python3で同じコールがOSErrorを投げること:

Traceback (most recent call last): 
    File "foo.py", line 113, in check_file 
    pos = infile.tell() 
OSError: telling position disabled by next() call 

私のユースケースは、大きなCSVファイルを読み込むためのプログレスバーを作っています。総ライン数を計算するには高価すぎるため、余分なパスが必要です。おおよその値は十分ですが、バッファやその他のノイズ源は気にしません。10秒か10分かかりますか?

問題を再現するための簡単なコード。これは、予想通りのPython 2.7では動作するのですが、Pythonの3にスロー:

file_size = os.stat(path).st_size 
with open(path, "r") as infile: 
    reader = csv.reader(infile) 
    for row in reader: 
     pos = infile.tell() # OSError: telling position disabled by next() call 
     print("At byte {} of {}".format(pos, file_size)) 

この回答https://stackoverflow.com/a/29641787/321772は問題がnext()方法は反復中tell()を無効にすることであることを示唆しています。代わりに手作業で行ごとに読むのではなく、そのコードがCSVモジュールの中にあるので、私はそれを得ることができません。私はまた、tell()を無効にすることでPython 3が得たことを理解できません。

だから、Python 3でファイルの行を反復処理する間にバイトオフセットを見つけるにはどうすればよいでしょうか?

+0

あなたは 'enumerate'を使って行番号を返すことができます。このように、ファイルを2回走査しなくても、ユーザに便利なものを与えることができます。 –

+0

@MaartenFabréもちろん、スクリプトがスタックされていないことを示すためだけに行番号を表示すると便利です。長さを知らない(すなわち、標準入力から読む)。しかし、「55%done、2 minutes remaining」を印刷するのは、「10,543,000行を読む」よりもはるかに優れています。 – Adam

答えて

4

。 csvモジュールは、reader呼び出しの最初のパラメータが、それぞれのnextコールで1行を返すイテレータになることを期待しています。したがって、イテレータラッパーを使用して文字数をカウントするのではなく、カウントを正確にすることができます。バイナリモードでファイルを開くことができます。しかし、実際には、 csvモジュールで期待される行末変換がないため、これは問題ありません。

だから、可能なラッパーは次のとおりです。

class SizedReader: 
    def __init__(self, fd, encoding='utf-8'): 
     self.fd = fd 
     self.size = 0 
     self.encoding = encoding # specify encoding in constructor, with utf8 as default 
    def __next__(self): 
     line = next(self.fd) 
     self.size += len(line) 
     return line.decode(self.encoding) # returns a decoded line (a true Python 3 string) 
    def __iter__(self): 
     return self 

あなたのコードは、その後になる:

file_size = os.stat(path).st_size 
with open(path, "rb") as infile: 
    szrdr = SizedReader(infile) 
    reader = csv.reader(szrdr) 
    for row in reader: 
     pos = szrdr.size # gives position at end of current line 
     print("At byte {} of {}".format(pos, file_size)) 

ここで良いニュースが引用さで改行を含め、あなたはcsvモジュールのすべての力を保つということですfields ...

+0

これは動作します。あなたはエンコーディングについて心配する必要はありませんが、与えられたものを取り出し、長さを見つけてそれを返すだけです。そうすれば、デコード動作を変更することはありません。同じコードがPython 2と3の両方で動作するように 'def next(self):return self .__ next __()'が必要であることにも注意してください。 – Adam

+0

@Adam:質問は特にPython 3に関するものでした。バイナリモードで読み込んだ内容をデコードしないと、文字列ではなくバイトが得られます。 csvモジュールはPython2とPython3では全く異なった動作をしますが、互換性のあるコードを提供しようとしたわけではありません。確かに可能ですが、より複雑になります。 –

+0

真ですが、問題はバイナリモードでファイルを開きません。 – Adam

0

特にcsvモジュールがない場合は快適です。 「

import os, csv 

file_size = os.path.getsize('SampleCSV.csv') 
pos = 0 

with open('SampleCSV.csv', "r") as infile: 
    for line in infile: 
     pos += len(line) + 1 # 1 for newline character 
     row = line.rstrip().split(',') 
     print("At byte {} of {}".format(pos, file_size)) 

しかし、これ自体は\含むフィールドの場合には動作しない場合があります

例を:あなたのような何かを行うことができます。1,"Hey, you..",22:04をこれらも正規表現を使用しての世話をすることができますが

関連する問題