在Windows中读取多字节文本文件 - 它如何检测换行符? (Python 2)

时间:2016-07-15 17:50:30

标签: python windows unicode

我认为这是一个Unicode世界的警告 - >您不能正确处理字节流作为写入而不知道编码是什么。如果您假设编码,那么您可能会显示有效但不正确的字符。

这是一个测试 - 一个带有写作的文件:

hi1
hi2

使用2字节Unicode编码存储在磁盘上:

Hex editor view of file

Windows换行符\r\n存储为四字节序列0D 00 0A 00。使用默认编码在Python 2中打开它,我认为它期望ASCII每个字符1个字节(或只是一个字节流),它读取:

>>> open('d:/t/hi2.txt').readlines()
['\xff\xfeh\x00i\x001\x00\r\x00\n', 
 '\x00h\x00i\x002\x00']

它没有将两个字节解码为一个字符,但是四字节行结束序列已经被检测为两个字符并且该文件已被正确地分成两行

据推测,Windows按“文本模式”打开文件,如下所述:Difference between files writen in binary and text mode

并将这些行提供给Python。但是Windows如何知道该文件是多字节编码的,并且根据问题顶部的警告,在没有被告知的情况下查找四个字节的换行符?

  • Windows 猜测,启发式 - 因此可能是错误的吗?
  • Unicode的设计是否更加聪明,是什么让Windows换行模式在编码中明确无误?
  • 我的理解是错误的,并且有一种正确的方法来处理任何文本文件而不事先告知编码?

2 个答案:

答案 0 :(得分:1)

此案例的结果与Windows或Microsoft C运行时的标准I / O实现无关。如果在Linux系统上使用Python 2进行测试,您将看到相同的结果。这就是file.readlines(2.7.12源链接)在Python 2中的工作原理。参见第1717行,p = (char *)memchr(buffer+nfilled, '\n', nread),然后是第1749行,line = PyString_FromStringAndSize(q, p-q)。天真地消耗最多\n个字符,这就是实际的UTF-16LE \n\x00序列被拆分的原因。

如果您使用Python 2的通用换行模式打开了文件,例如open('d:/t/hi2.txt', 'U')\r\x00序列会被天真地翻译为\n\x00readlines的结果将改为['\xff\xfeh\x00i\x001\x00\n, \x00\n', '\x00h\x00i\x002\x00']

因此,您的初步假设是正确的。您需要知道编码,或者至少知道在文件开头查找Unicode BOM(字节顺序标记),例如\xff\xfe,表示UTF-16LE(小端)。为此,我建议在Python 2.7中使用io模块,因为它正确处理换行符。另一方面,codecs.open需要在包装文件上使用二进制模式,并忽略通用换行模式:

>>> codecs.open('test.txt', 'U', encoding='utf-16').readlines()
[u'hi1\r\n', u'hi2']

io.open会返回内置支持通用换行符的TextIOWrapper

>>> io.open('test.txt', encoding='utf-16').readlines()
[u'hi1\n', u'hi2']

关于Microsoft的CRT,它默认为ANSI文本模式。 Microsoft的ANSI代码页是ASCII的超集,因此CRT的换行符适用于使用ASCII兼容编码(如UTF-8)编码的文件。另一方面,ANSI文本模式不适用于UTF-16编码文件,即它不会删除UTF-16LE BOM(\xff\xfe)并且不会转换换行符:

>>> open('test.txt').read()
'\xff\xfeh\x00i\x001\x00\r\x00\n\x00h\x00i\x002\x00' 

因此,对于UTF-16编码文件使用标准I / O文本模式需要非标准ccs标志,例如fopen("d:/t/hi2.txt", "rt, ccs=UNICODE")。 Python不支持对开放mode的此Microsoft扩展,但它确实使_open中的CRT的低I / O(POSIX)_reados函数可用模块。虽然它可能让POSIX程序员感到惊讶,但微软的低I / O API也支持文本模式,包括Unicode。例如:

>>> O_WTEXT = 0x10000
>>> fd = os.open('test.txt', os.O_RDONLY | O_WTEXT)
>>> os.read(fd, 100)
'h\x00i\x001\x00\n\x00h\x00i\x002\x00'
>>> os.close(fd)

{1}}常量不能在Windows Python中直接使用,因为使用O_WTEXT将此模式作为Python file打开文件描述符是不安全的。 CRT期望所有宽字符缓冲区都是os.fdopen大小的倍数,即2的倍数。否则它会调用杀死进程的无效参数处理程序。例如(使用cdb调试器):

wchar_t

同样适用于>>> fd = os.open('test.txt', os.O_RDONLY | O_WTEXT) >>> os.read(fd, 7) ntdll!NtTerminateProcess+0x14: 00007ff8`d9cd5664 c3 ret 0:000> k8 Child-SP RetAddr Call Site 00000000`005ef338 00007ff8`d646e219 ntdll!NtTerminateProcess+0x14 00000000`005ef340 00000000`62db5200 KERNELBASE!TerminateProcess+0x29 00000000`005ef370 00000000`62db52d4 MSVCR90!_invoke_watson+0x11c 00000000`005ef960 00000000`62db0cff MSVCR90!_invalid_parameter+0x70 00000000`005ef9a0 00000000`62db0e29 MSVCR90!_read_nolock+0x76b 00000000`005efa40 00000000`1e056e8a MSVCR90!_read+0x10d 00000000`005efaa0 00000000`1e0c3d49 python27!Py_Main+0x12a8a 00000000`005efae0 00000000`1e1146d4 python27!PyCFunction_Call+0x69 _O_UTF8

答案 1 :(得分:0)

首先要做的事情是:将文件作为文本打开,指示正确的编码,并采用显式文本模式。

如果您仍在使用Python 2.7,请使用codecs.open代替open。在Python 3.x中,只需使用open:

import codecs
myfile = codecs.open('d:/t/hi2.txt', 'rt', encoding='utf-16')

你应该能够继续努力。

其次,那里有什么可能:由于你没有指定你是以二进制模式打开文件,Windows在" text"中打开它。模式 - Windows确实知道编码,因此,可以在行中找到\r\n序列 - 它分别读取行,执行行尾转换 - 使用utf-16 - 并传递那些 - Python的16个字节。

在Python方面,您可以使用这些值,只需将它们解码为文本:

[line.decode("utf-16" for line in open('d:/t/hi2.txt')]

而不是

 open('d:/t/hi2.txt').readlines()