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

Posted

技术标签:

【中文标题】从文件末尾寻找抛出不受支持的异常【英文标题】:Seeking from end of file throwing unsupported exception 【发布时间】:2014-02-27 07:05:02 【问题描述】:

我有这段代码 sn-p,我正在尝试使用 python 从文件末尾向后搜索:

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

这会在运行时引发以下异常:

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

我在这里错过了什么吗?

【问题讨论】:

Python 3 仅支持从文件开头查找文本文件。如果要获取文件的最后三行,可以使用 deque(f, 3) 仅遍历这些行。 您不能再按设计寻找文本文件中的任意位置。这是因为像 UTF-8 这样的编码每个字符的字节数是不可预测的。 Seek() 不能盲目地寻找文件中的某个位置并期望位于字符的开头。 【参考方案1】:

来自documentation,适用于 Python 3.2 及更高版本:

在文本文件中(那些在模式字符串中没有b 打开的文件),只允许相对于文件开头的搜索(例外是搜索到带有seek(0, 2) 的文件末尾)。

这是因为文本文件在编码字节和它们所代表的字符之间没有一一对应的关系,所以seek无法告诉在文件中跳转到哪里移动一定数量的字符.

如果您的程序可以处理原始字节,您可以将程序更改为读取:

f = open('D:\SGStat.txt', 'ab')
f.seek(-3, 2)

注意模式字符串中的b,用于二进制文件。 (还要注意删除了多余的f.seek(0, 2) 调用。)

但是,您应该知道,在读取或写入文本时添加 b 标志可能会产生意想不到的后果(例如使用多字节编码),实际上是 changes the type of data read or written。

【讨论】:

【参考方案2】:

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

来自readthedocs:

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

the documentation 支持这一点,上面写着:

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

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

f.seek(-1, 1)   # seek -1 from current position

在 Python 3 中看起来像这样:

f.seek(f.tell() - 1, os.SEEK_SET)   # os.SEEK_SET == 0

解决方案

将这些信息放在一起,我们可以实现 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

【讨论】:

很好的答案。最后,一个没有使用二进制模式副作用的解决方案 这不起作用! 并导致 UnicodeDecodeError。示例:创建一个以a£b 开头的文本文件。打开文件和f.seek(2, os.SEEK_SET)。然后print(f.read(1))。这是因为对于文本文件 f.tell() 是一个不透明的数字和“the only valid offset values are those returned from the f.tell()”。 f.tell() - f.tell() 从未返回 3,因此没有任何 garentee 会使文件对象处于可读状态。它可能位于多字节 utf8 字符的中间。 具有讽刺意味的是,这个答案甚至引用了码头上的一段话,说这是“未定义的行为”。 f.tell() - 1 不是“tell() 返回的偏移量”,也不是有效的搜索偏移量。这个答案不起作用。【参考方案3】:

Eric Lindsey's answer 不起作用,因为 UTF-8 文件的每个字符可以有多个字节。更糟糕的是,对于我们这些以英语为第一语言并且只使用英语文件的人来说,它可能会足够长进入生产代码并真正破坏事情。


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

...但它现在确实适用于 Python 3.7 中的 UTF-8。

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

f.tell() 的结果仍然是 UTF-8 文件在文件中的字节位置,至少现在是这样。因此,当您随后 f.read() 时,f.seek() 到无效偏移量将引发 UnicodeDecodeError,这可以通过 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)

【讨论】:

请注意,在调整搜索位置直到找到有效位置之前,您可以在文件中的 某处 进行搜索,但您通常不知道刚刚跳过了多少个字符。对于答案中的示例代码来说,这恰好不是问题,但如果您尝试执行类似f.seek(f.tell() - 1000, os.SEEK_SET) 之类的操作来跳回 1000 个字符,这将是一个问题。【参考方案4】:

为了使用从当前位置查找并结束,您必须以二进制模式打开文本文件。请参阅此示例,其中我创建了一个文件“nums.txt”并将“ABCDEFGHIJKLMNOPQRSTUVWXYZ”放入文件中。我从文件中读取字符串“PYTHON”的字母并显示相同。查看我在 anaconda 4.2 中的 python 3.6 windows 中运行的代码

    >>> file=open('nums.txt','rb')
    >>> file.seek(15,0)
    15
    >>> file.read(1).decode('utf-8')
    'P'
    >>> file.seek(8,1)
    24
    >>> file.read(1).decode('utf-8')
    'Y'
    >>> file.seek(-7,2)
    19
    >>> file.read(1).decode('utf-8')
    'T'
    >>> file.seek(7,0)
    7
    >>> file.read(1).decode('utf-8')
    'H'
    >>> file.seek(6,1)
    14
    >>> file.read(1).decode('utf-8')
    'O'
    >>> file.seek(-2,1)
    13
    >>> file.read(1).decode('utf-8')
    'N'

【讨论】:

这不是预期的,以 bin 格式打开文件可能会产生意想不到的后果。 @Coddy 有什么意想不到的后果?如果您知道编码(并且您应该知道是否要将文件视为文本),那么任何固定宽度编码(包括 ASCII、Latin-1 以及 UTF-16 和 UTF-32 编码)都会为您提供可预测的偏移量。 UTF-8 只需要一点位掩码。 UTF-8 不是 ASCII。对于代码点 >= 128,UTF-8 的每个字符并非只有 1 个字节。这仅在文件中的每个字符都有一个 Unicode 代码点 £ , , ¥

以上是关于从文件末尾寻找抛出不受支持的异常的主要内容,如果未能解决你的问题,请参考以下文章

Firebase - getToken() 抛出不受支持的 MIME 类型

循环 IEnumerable<T> 抛出不支持的异常

为啥 collections.sort 在 Java 中按比较器排序时会抛出不支持的操作异常?

ContentDisposition 类抛出不一致的异常

Blazor Wasm 发送邮件抛出异常 System.PlatformNotSupportedException: System.Net.Dns:GetHostByName 在此平台上不受支持

java.io.EOFException这是个啥异常应该怎么解决