python seek() 和 read() 以不同的方式计算文件位置

Dan*_*iel 1 python file-io

我正在制作一个脚本,在文件的特定位置显示文本。但是,seek() 和 read() 的计数方式存在差异。它是这样的。

我的文本文件是:

1
%
2
%
?
%
4
%
5
%
6
Run Code Online (Sandbox Code Playgroud)

这 '?' 第 5 行是一个单杠(unicode 0x2015)而不是破折号。'%' 用作分隔符。

以下数据用作文件索引

0 2 ['1\n']
4 2 ['2\n']
8 4 ['?\n']
14 2 ['4\n']
18 2 ['5\n']
22 2 ['6\n']
Run Code Online (Sandbox Code Playgroud)

第一列是字符串在文件中的位置(数字),第二列是长度,第三列是要显示的文本(文本文件第 1、3、5、7、9、11 行中的数字) .

我正在尝试在特定位置读取文件,如下所示:

f = open('myfile.txt', 'r', encoding='utf-8')
f.seek(start)
text = f.read(length)
f.close()
Run Code Online (Sandbox Code Playgroud)

其中 'start' 和 'length' 是索引文件的第一列和第二列,而 'text' 是要显示的文本。这非常适合显示索引文件中除第 5 行(带水平条的那一行)之外的所有行的内容,因为 seek() 将水平条的长度解释为 3,因此索引中的总长度为 4 file(3 代表水平条,1 代表 '\n'),而 read() 将水平线的长度解释为只有一条,从而创建以下输出:

?
%
(blank space)
Run Code Online (Sandbox Code Playgroud)

也就是说,它包括水平条、它的 '\n'、分隔符和它的 '\n'(四个字符)。这种影响是累积的,越多的横条或任何其他非 utf-8 的 unicode 字符都会增加错误显示的行数。

关于如何解决这个问题的任何想法?

tde*_*ney 8

在Python 3中,当您以文本模式打开文件(例如“r”)时,您和原始文件之间有一个解码器。在本例中,它是 UTF-8 解码器。“文件位置”实际上没有意义,因为文本级别的字符索引与文件中的字节索引不同。此外,Python 在后台进行缓存以帮助解码。

解决方案是读取二进制并稍后进行解码

f = open('myfile.txt', 'rb')
f.seek(start)
text = f.read(length).decode(encoding='utf-8')
f.close()
Run Code Online (Sandbox Code Playgroud)


aba*_*ert 5

seek总是以字节为单位,*不是字符,即使对于以文本模式打开的文件也是如此。

否则它不可能远程有效地工作 - UTF-8 文本文件中的第 100 万个字符可能位于字节 1,000,000 或字节 2,739,184,而找出答案的唯一方法是回到开头并编码 999,999 个字符。**

read如果您处于二进制模式,则仅读取字节;在文本模式下,这些字节会被动态解码为 Unicode 字符串。(因为您是按顺序读取文件,所以这通常不是性能问题——但如果是,您总是使用二进制模式。)

如果您有一个想要返回的已知位置,您可以通过调用来“标记”它tell,然后seek稍后返回,但除此之外,查找在文本文件中不是很有用,除了开始或结束当然是文件。


* 事实上,它甚至没有记录为文本文件的字节;返回的 0 或“不透明数字”以外的任何内容都会tell产生“未定义的行为”。我相信它总是会寻找确切的指定字节位置——但由于解码器管道的工作方式,即使您不寻找字符的中间,这也会导致 mojibake,尤其是使用移位代码的编码。为了处理这些情况,tell制作可以在以后恢复的特殊快照seek,但当然文件中的某些随机点没有快照。

** 这并不完全正确 - 您可以在阅读时或在尝试寻找时,甚至可以通过提前阅读来构建偏移量表。但这绝对不是您希望 Python 对每个文件执行的操作,只是在您想按字符索引查找的极少数情况下;这是您想要专门针对您关心的罕见情况进行调整的内容。该linecache模块——它在标准库中,因为调试器需要它——做了大致相同的工作,并且只要你忽略关于分词器的位,它就会带有非常可读的源代码,所以如果你想自己构建一个字符索引器,它可能是很好的示例代码开始。