web-dev-qa-db-ja.com

file.tell()の不整合

この方法でファイルを反復処理するときに、その理由を誰かが知っていますか?

入力:

f = open('test.txt', 'r')
for line in f:
    print "f.tell(): ",f.tell()

出力:

f.tell(): 8192
f.tell(): 8192
f.tell(): 8192
f.tell(): 8192

私は一貫してtell()から間違ったファイルインデックスを取得しますが、readlineを使用すると、tell()に適切なインデックスを取得します。

入力:

f = open('test.txt', 'r')
while True:
    line = f.readline()
    if (line == ''):
        break
    print "f.tell(): ",f.tell()

出力:

f.tell(): 103
f.tell(): 107
f.tell(): 115
f.tell(): 124

私はpython 2.7.1ところで実行しています。

40
nigp4w rudy

開いているファイルをイテレーターとして使用すると、先読みバッファーを使用して効率が向上します。その結果、行をループすると、ファイルポインタがファイル全体に大きなステップで進みます。

ファイルオブジェクト ドキュメントから:

Forループをファイルの行をループする最も効率的な方法(非常に一般的な操作)にするために、next()メソッドは非表示の先読みバッファーを使用します。先読みバッファを使用した結果、next()を他のファイルメソッド(readline()など)と組み合わせても正しく機能しません。ただし、seek()を使用してファイルを絶対位置に再配置すると、先読みバッファーがフラッシュされます。

.tell()に依存する必要がある場合は、ファイルオブジェクトをイテレータとして使用しないでください。代わりに、.readline()をイテレータに変えることができます(パフォーマンスがいくらか低下します)。

_for line in iter(f.readline, ''):
    print f.tell()
_

これは iter() functionsentinel引数を使用して、呼び出し可能オブジェクトをイテレーターに変換します。

61
Martijn Pieters

答えは、Python 2.7ソースコード(_fileobject.c_)の次の部分にあります。

_#define READAHEAD_BUFSIZE 8192

static PyObject *
file_iternext(PyFileObject *f)
{
    PyStringObject* l;

    if (f->f_fp == NULL)
        return err_closed();
    if (!f->readable)
        return err_mode("reading");

    l = readahead_get_line_skip(f, 0, READAHEAD_BUFSIZE);
    if (l == NULL || PyString_GET_SIZE(l) == 0) {
        Py_XDECREF(l);
        return NULL;
    }
    return (PyObject *)l;
}
_

ご覧のとおり、fileのイテレータインターフェイスは8KBのブロックでファイルを読み取ります。これは、f.tell()がそのように動作する理由を説明しています。

ドキュメント 提案 パフォーマンス上の理由で作成されています(また、先読みバッファーの特定のサイズを保証するものではありません)。

12
NPE

同じ先読みバッファの問題が発生し、 Martijnの提案 を使用して解決しました。

それ以来、私はそのようなことをしようとしている他の人のために私の解決策を一般化しました:

https://github.com/loisaidasam/csv-position-reader

ハッピーCSV解析!