web-dev-qa-db-ja.com

「OSError:next()呼び出しによって無効にされた位置を通知する」を解決する方法

ファイル編集システムを作成していますが、バイトベースではなくラインベースのtell()関数を作成したいと考えています。この関数は、open(file)呼び出しで「withloop」内で使用されます。この関数は、次のクラスの一部です。

self.f = open(self.file, 'a+')
# self.file is a string that has the filename in it

以下は元の関数です(行とバイトを返したい場合はchar設定もあります):

def tell(self, char=False):
    t, lc = self.f.tell(), 0
    self.f.seek(0)
    for line in self.f:
        if t >= len(line):
            t -= len(line)
            lc += 1
        else:
            break
    if char:
        return lc, t
    return lc

私がこれで抱えている問題は、これがOSErrorを返し、システムがファイルを反復処理する方法と関係があるということですが、私は問題を理解していません。助けてくれる人に感謝します。

16
Brandon Gomes

古いバージョンのPython 3を使用しており、MacではなくLinuxを使用していますが、エラーに非常に近いものを再作成できました。

_IOError: telling position disabled by next() call
_

[〜#〜] io [〜#〜]エラーであり、[〜#〜] os [ 〜#〜]エラーですが、それ以外は同じです。奇妙なことに、私はあなたのopen('a+', ...)を使用してそれを引き起こすことができませんでしたが、読み取りモードでファイルを開いたときだけです:open('r+', ...)

さらに厄介なのは、エラーが__io.TextIOWrapper_、Pythonの__pyio.py_ファイルで定義されているように見えるクラスから発生していることです。 。私は「現れる」ことを強調します。理由は次のとおりです。

  1. そのファイルのTextIOWrapperには、__telling_のような属性があり、それ自体を__io.TextIOWrapper_と呼んでいるオブジェクトにはアクセスできません。

  2. __pyio.py_のTextIOWrapperクラスは、読み取り可能、書き込み可能、​​またはランダムアクセスファイルを区別しません。両方が機能するか、両方が同じIOErrorを発生させる必要があります。

とにかく、__pyio.py_ファイルで説明されているTextIOWrapperクラス反復の進行中にtellメソッドを無効にします。これはあなたが遭遇しているもののようです(コメントは私のものです):

_def __next__(self):
    # Disable the tell method.
    self._telling = False
    line = self.readline()
    if not line:
        # We've reached the end of the file...
        self._snapshot = None
        # ...so restore _telling to whatever it was.
        self._telling = self._seekable
        raise StopIteration
    return line
_

tellメソッドでは、ほとんどの場合、ファイルの終わりに達する前に反復からbreakを除外し、__telling_を無効のままにします(False)。

__telling_をリセットするもう1つの方法は、flushメソッドですが、反復の進行中に呼び出された場合も失敗します。

_IOError: can't reconstruct logical file position
_

これを回避する方法は、少なくとも私のシステムでは、TextIOWrapperseek(0)を呼び出すことです。これにより、すべてが既知の状態に復元されます(そして、掘り出し物でflushが正常に呼び出されます)。

_def tell(self, char=False):
    t, lc = self.f.tell(), 0
    self.f.seek(0)
    for line in self.f:
        if t >= len(line):
            t -= len(line)
            lc += 1
        else:
            break
    # Reset the file iterator, or later calls to f.tell will
    # raise an IOError or OSError:
    f.seek(0)
    if char:
        return lc, t
    return lc
_

それがシステムの解決策ではない場合は、少なくともどこから探し始めるかを教えてくれるかもしれません。

PS:alwaysが行番号と文字オフセットの両方を返すことを検討する必要があります。完全に異なる型を返すことができる関数は処理が困難です---呼び出し元が必要のない値を破棄する方がはるかに簡単です。

13
Kevin J. Chase

これが元のエラーであったかどうかはわかりませんが、次のようにファイルの行ごとの反復内でf.tell()を呼び出そうとすると、同じエラーが発生する可能性があります。

with open(path, "r+") as f:
  for line in f:
    f.tell() #OSError

これは、次のように簡単に置き換えることができます。

with open(path, mode) as f:
  line = f.readline()
  while line:
    f.tell() #returns the location of the next line
    line = f.readline()
22
Héctor

この問題の簡単な回避策:

とにかく最初からファイルを反復処理しているので、専用の変数を使用して現在の場所を追跡するだけです。

_file_pos = 0
with open('file.txt', 'rb') as f:
    for line in f:
        # process line
        file_pos += len(line)
_

これで、_file_pos_は常になり、file.tell()tellあなたになります。これは、バイト位置でのtellおよびseek作業としてASCIIファイルに対してのみ機能することに注意してください。行ベースでの作業は、文字列をバイトからユニコード文字列に変換するのは簡単です。

2
moritzschaefer