首页 > 解决方案 > Python 3 tell() 在追加+读取模式下与文件指针不同步

问题描述

与 Python 2 (2.7.15) 不同,当打开二进制文件进行附加和读取时,我在 Python 3 (3.6.5) 中看到了奇怪的 f.tell() 行为。如果在当前查找位置不在文件末尾时写入 n 个字节,则似乎会按预期发生以下情况:

  1. 文件指针移动到文件末尾。
  2. 写入 n 个字节。
  3. n 被添加到文件指针。

但是,f.tell() 似乎没有注意到第 1 步,因此 f.tell() 返回的值与实际文件指针相比偏移了一个恒定的负数。我在 Windows 和 Linux 上都看到了相同的情况。

这是一些演示该问题的 Python 3 代码:

import io

# Create file with some content
f = open('myfile', 'wb')
f.write(b'abc')
print(f.tell())                 # 3
f.close()

# Now reopen file in append+read mode and check that appending works
f = open('myfile', 'a+b')
print(f.tell())                 # 3
f.write(b'def')                 # (Append)
print(f.tell())                 # 6

# Now seek to start of file and append again -> tell() gets out of sync!
print(f.seek(0))                # 0
print(f.tell())                 # 0
f.write(b'ghi')                 # (Append)
print(f.tell())                 # 3!!! (expected 9)
f.write(b'jkl')                 # (Append)
print(f.tell())                 # 6!!! (expected 12)

# Try calling seek without moving file pointer -> tell() starts working again
print(f.seek(0, io.SEEK_CUR))   # 12 (correct)
print(f.tell())                 # 12 (correct)

# Read whole file to verify its contents
print(f.seek(0))                # 0
print(f.read())                 # b'abcdefghijkl' (correct)
f.close()

Python 3 文档有关于在文本文件上使用 seek()/tell() 的警告(参见io.TextIOBase),以及关于某些平台上的附加模式的警告(参见open()):

[...] 'a' 用于追加(在某些 Unix 系统上,这意味着所有写入都追加到文件的末尾,而不管当前的查找位置如何)。

但是我使用的是二进制文件,并且无论查找位置如何,写入似乎都附加到文件的末尾,所以我的问题是不同的。

我的问题:这种行为是否在某处(直接或间接)记录在案,或者是否至少记录了该行为未指定?

编辑:

文本文件似乎没有这个问题(在 Python 2 和 3 中都没有),所以只有二进制文件不能按预期工作。

Python 3 文档(io.TextIOBase)声明 tell() 返回文本文件的“不透明”值(即未指定该值如何表示位置),因为没有提及这是否也适用对于二进制文件,有人可能会推测我的问题与这种不透明度有关。但是,这不可能是真的,因为即使是不透明的值也必须 - 当给 seek() 时 - 将文件指针返回到调用 tell() 时的位置,并且在上面的示例中,当 tell() 返回前 6然后 12 在同一文件位置(文件末尾),只有 seek(12) 实际上会将文件指针再次移动到该位置。所以值 6 不能用文件指针不透明度来解释。

标签: pythonpython-3.x

解决方案


就像我在issue36411上回答的那样

我不确定这是一个错误。当您将二进制数据写入文件时(默认使用 BufferedIOBase)。它实际上将数据写入缓冲区。这就是 tell() 不同步的原因。您可以按照下面的仪器。例如,在写入后调用 flush() 以获取correct answer.

写入此对象时,数据通常被放入内部缓冲区。缓冲区将在各种条件下写入底层 RawIOBase 对象,包括:

  1. 当缓冲区对于所有待处理的数据来说太小时;
  2. 当 flush() 被调用时;
  3. 当请求 seek() 时(对于 BufferedRandom 对象);
  4. 当 BufferedWriter 对象关闭或销毁时。
  1. https://docs.python.org/3/library/io.html#io.BufferedWriter

推荐阅读