如何解决"OSError:通过next()调用告诉位置被禁用"

Bra*_*mes 15 python error-handling file-io next

我正在创建一个文件编辑系统,并希望创建一个基于行的tell()函数而不是基于字节的函数.此函数将在打开(文件)调用的"with循环"内使用.此函数是具有以下内容的类的一部分:

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

以下是原始函数(如果您想要行和字节返回,它还有一个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
Run Code Online (Sandbox Code Playgroud)

我遇到的问题是,这会返回一个OSError,它与系统如何迭代文件有关,但我不明白这个问题.感谢任何能提供帮助的人.

小智 22

我不知道这是不是原始错误,但如果你尝试在文件的逐行迭代中调用f.tell(),你可以得到相同的错误:

with open(path, "r+") as f:
  for line in f:
    f.tell() #OSError
Run Code Online (Sandbox Code Playgroud)

可以很容易地用以下代替:

with open(path, mode) as f:
  line = f.readline()
  while line:
    f.tell() #returns the location of the next line
    line = f.readline()
Run Code Online (Sandbox Code Playgroud)

  • 另外,如果你使用的是足够现代的 Python 版本,你可以在不使用双 `line = f.readline()` 的情况下编写此代码,只需将 `while line:` 替换为 `while line:= f.readline():` (5认同)

Kev*_*ase 12

我有一个旧版本的Python 3,我在Linux而不是Mac,但我能够重新创建一些非常接近你错误的东西:

IOError: telling position disabled by next() call
Run Code Online (Sandbox Code Playgroud)

一个IO错误,而不是一个操作系统错误,但在其他方面是相同的.奇怪的是,我不能使用你的open('a+', ...),但只有在读取模式下打开文件时:open('r+', ...).

进一步混淆的是错误来自于_io.TextIOWrapper一个似乎在Python _pyio.py文件中定义的类......我强调"出现",因为:

  1. TextIOWrapper文件中的属性具有类似于_telling我无法在调用自身的任何对象上访问的属性_io.TextIOWrapper.

  2. TextIOWrapper_pyio.py不作可读,可写,或随机存取文件之间的任何区别.要么两者都应该起作用,要么两者都应该提高IOError.

无论如何,文件中TextIOWrapper描述的类在迭代进行时禁用该方法.这似乎是你遇到的(评论是我的):_pyio.pytell

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
Run Code Online (Sandbox Code Playgroud)

在你的tell方法中,你几乎总是break在它到达文件末尾之前离开迭代,离开_tellingdisabled(False):

另一种重置方法_tellingflush方法,但如果在迭代进行过程中调用它也会失败:

IOError: can't reconstruct logical file position
Run Code Online (Sandbox Code Playgroud)

解决这个问题的办法,至少我的系统上,就是要seek(0)上了TextIOWrapper,这一切都恢复到已知状态(成功调用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
Run Code Online (Sandbox Code Playgroud)

如果这不是您系统的解决方案,它至少可以告诉您从哪里开始寻找.

PS:您应该考虑始终返回行号和字符偏移量.可以返回完全不同类型的函数很难处理 - 调用者只需丢弃她或她不需要的值就容易多了.


mor*_*fer 6

只是这个问题的快速解决方法:

无论如何,当您从头开始迭代文件时,只需使用专用变量跟踪您的位置:

file_pos = 0
with open('file.txt', 'rb') as f:
    for line in f:
        # process line
        file_pos += len(line)
Run Code Online (Sandbox Code Playgroud)

现在file_pos将永远是,file.tell()告诉你什么。请注意,这仅适用于 ASCII 文件,因为告诉和寻找字节位置的工作。虽然以行为基础工作,但将字符串从字节转换为 unicode 字符串很容易。