如何解决“OSError: telling position disabled by next() call”问题

36
我正在创建一个文件编辑系统,并希望使用基于行的tell()函数,而不是基于字节的函数。这个函数将在"with循环"中与open(file)调用一起使用。这个函数是一个类的一部分,它具有以下功能:
self.f = open(self.file, 'a+')
# self.file is a string that has the filename in it

以下是原始函数(如果你想要换行和字节返回,也可以设置字符):
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,并且与系统如何迭代文件有关,但我不明白其中的问题。感谢任何能够帮助的人。


很难回答你的问题,因为没有看到你的代码其余部分。(我在Linux上只使用函数无法重现这个错误。) 你可能想要查阅OSError的属性,它会给你(和我们)一些额外的信息。我的第一个问题是,既然这是一个_OS_错误:你的操作系统是什么?此外(可能相关):为什么/如何以追加模式打开文件,然后在其中进行seek - Kevin J. Chase
我正在以追加模式打开它,因为假定在创建实例之前文件不存在(如您所知,'a'模式会在文件尚不存在时创建该文件)。我希望能够节省代码中检查文件是否存在的空间。我的操作系统是Mac OS X Yosemite,但我不认为这与苹果有关。 - Brandon H. Gomes
5个回答

57

我不知道这是否是最初的错误,但如果您尝试在按行迭代文件的过程中调用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()

这与“内部”无关,而是指在没有绝对查找的情况下是否发生过之前的操作。 - Antti Haapala -- Слава Україні
非常好的解决方案,谢谢! - datatraveller1
4
如果你使用的是足够现代的 Python 版本,你可以用 while line:= f.readline(): 替换 while line: 来避免使用双倍的 line = f.readline()。请注意,这个替换不会改变原来的意思,只是更加简洁易懂。 - Greg0ry

24

我使用的是较旧版本的Python 3,并且我使用的是Linux而不是Mac,但我已经能够创建非常接近你错误的情况:

IOError: telling position disabled by next() call

一个IO错误,不是操作系统错误,但其他方面相同。令人惊奇的是,我无法使用您的“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的方法是使用flush方法,但如果在迭代过程中调用它也会失败:

IOError: can't reconstruct logical file position

在我系统上避免这个问题的方法是在TextIOWrapper上调用seek(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:您应该考虑始终返回行号和字符偏移量。能够返回完全不同类型的函数很难处理 --- 对于调用者来说,只需丢弃他或她不需要的值要容易得多。

1
非常感谢您的帮助!我的问题似乎是在文件迭代(逐行)期间无法调用(内置的)tell()方法。我找到了一个解决方法,而且您的答案确实有所帮助。再次感谢! - Brandon H. Gomes
@BrandonGomes:你介意和我分享你的解决方案吗? - marscher
1
抱歉 @marscher,我没有这段代码了。它来自一台旧电脑。我认为答案是存储有关文件迭代器的一些元数据。您可以重新编写 next 函数。 - Brandon H. Gomes
1
为了给这个答案增加一些色彩,这发生在cpython中的地方是这里。至于为什么会发生这种情况,可能是由于缓存导致实际位置不是100%准确。但是,在加载大型jsonl文件时仍然很有用,例如,只需使用f.buffer.tell() - heiner

8

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

既然你无论如何都要从文件开头迭代,那么只需使用一个专门的变量来跟踪你所在的位置:

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

现在,file_pos 的值始终是 file.tell() 告诉您的内容。请注意,这仅适用于 ASCII 文件,因为 tell 和 seek 使用字节位置。然而,在基于行的工作中,将字符串从字节转换为 Unicode 字符串很容易。

1
在py3中,由于使用了'rb',所以行的格式与预期相同(包括行终止符\r\n)- 因此这对于将光标倒回到行首非常有效 - 很棒! - Mr_and_Mrs_D

4

我也遇到了同样的错误:OSError: telling position disabled by next() call,通过在打开文件时添加 'rb' 模式来解决它。


2
错误信息非常清晰,但缺少一个细节:在文本文件对象上调用next会禁用tell方法。一个for循环会重复调用iter(f)上的next,对于文件来说,这恰好是f本身。我曾经遇到过类似的问题,尝试在循环内部调用tell而不是调用函数两次。
另一种解决方法是在不使用内置文件迭代器的情况下迭代文件。相反,您可以从iter函数的神秘二参数形式中制作一个几乎同样有效的迭代器。
for line in iter(f.readline, ''):

需要在csv中的每一行获取文件位置,并且csv.reader.__next__方法在文件对象上调用__next__。这帮助我解决了这个问题,同时仍然使用了csv.reader - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接