从文件末尾寻求抛出不支持的异常

ser*_*eek 24 python file python-3.3

我有这个代码片段,我试图使用python从文件末尾向后搜索:

f=open('D:\SGStat.txt','a');
    f.seek(0,2)
    f.seek(-3,2)
Run Code Online (Sandbox Code Playgroud)

这会在运行时抛出以下异常:

f.seek(-3,2)
io.UnsupportedOperation: can't do nonzero end-relative seeks
Run Code Online (Sandbox Code Playgroud)

我错过了什么吗?

jon*_*rpe 36

从Python 3.2及更高版本的文档:

在文本文件(那些b在模式字符串中没有打开的文件)中,只允许相对于文件开头的搜索(异常是寻找到文件结尾seek(0, 2)).

因此,您可以将程序更改为:

f = open('D:\SGStat.txt', 'ab')
f.seek(0, 2)
f.seek(-3, 2)
Run Code Online (Sandbox Code Playgroud)

但是,您应该知道b在读取或写入文本时添加标志可能会产生意想不到的后果(例如,使用多字节编码),实际上会更改读取或写入的数据类型.有关问题原因的更全面讨论以及不需要添加b标记的解决方案,请参阅此问题的另一个答案.


Eri*_*sey 28

现有的答案确实回答了这个问题,但没有提供解决方案.

来自readthedocs:

如果文件以文本模式打开(没有b),则只返回的偏移tell()是合法的.使用其他偏移会导致未定义的行为.

文档支持这一点,该文档说:

在文本文件中(那些b在模式字符串中没有打开的文件),只允许相对于文件[ os.SEEK_SET]的开头查找...

这意味着如果你有来自旧Python的代码:

f.seek(-1, 1)   # seek -1 from current position
Run Code Online (Sandbox Code Playgroud)

它在Python 3中看起来像这样:

f.seek(f.tell() - 1, os.SEEK_SET)   # os.SEEK_SET == 0
Run Code Online (Sandbox Code Playgroud)

将这些信息放在一起我们可以实现OP的目标:

f.seek(0, os.SEEK_END)              # seek to end of file; f.seek(0, 2) is legal
f.seek(f.tell() - 3, os.SEEK_SET)   # go backwards 3 bytes
Run Code Online (Sandbox Code Playgroud)

  • **这不起作用!**并会导致 UnicodeDecodeError。示例:创建一个以“a£b”开头的文本文件。打开文件和“f.seek(2, os.SEEK_SET)”。然后`print(f.read(1))`。这是因为对于文本文件, f.tell() 是一个不透明的数字,并且“[唯一有效的偏移值是从 f.tell() 返回的偏移值](https://docs.python.org/3/tutorial/inputoutput。 html#文件对象方法)”。f.tell() - 3 从未被 f.tell() 返回,因此没有任何保证它会使文件对象处于可读状态。它可能位于多字节 utf8 字符的中间。 (3认同)
  • 讽刺的是,这个答案甚至引用了码头上的一段话,说这是“未定义的行为”。 (3认同)
  • `f.tell() - 1` 不是“由 `tell()` 返回的偏移量”,也不是有效的查找偏移量。这个答案不起作用。 (2认同)

Phi*_*ing 6

Eric Lindsey 的答案不起作用,因为 UTF-8 文件每个字符可以有多个字节。更糟糕的是,对于我们这些以英语为第一语言并仅使用英语文件的人来说,它可能只工作足够长的时间才能进入生产代码并真正破坏东西。


以下答案基于未定义的行为

...但它现在确实适用于 UTF-8,至少可以追溯到 Python 3.7,至少可以达到 Python 3.12

要在文本模式下向后查找文件,只要正确处理由于UnicodeDecodeError查找不是 UTF-8 字符开头的字节而导致的问题,就可以这样做。由于我们正在向后查找,因此我们可以简单地向后查找一个额外的字节,直到找到字符的开头。

f.tell()至少目前,结果仍然是 UTF-8 文件在文件中的字节位置。因此,f.seek()当您随后使用无效偏移量时,将引发 UnicodeDecodeError f.read(),并且可以通过再次将其纠正f.seek()为不同的偏移量。至少现在这有效。

例如,寻找一行的开头(就在 之后\n):

pos = f.tell() - 1
if pos < 0:
    pos = 0
f.seek(pos, os.SEEK_SET)
while pos > 0:
    try:
        character = f.read(1)
        if character == '\n':
            break
    except UnicodeDecodeError:
        pass
    pos -= 1
    f.seek(pos, os.SEEK_SET)
Run Code Online (Sandbox Code Playgroud)