我认为这是一个Unicode世界的警告 - >您不能正确处理字节流作为写入而不知道编码是什么。如果您假设编码,那么您可能会显示有效但不正确的字符。
这是一个测试 - 一个带有写作的文件:
hi1
hi2
使用2字节Unicode编码存储在磁盘上:
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如何知道该文件是多字节编码的,并且根据问题顶部的警告,在没有被告知的情况下查找四个字节的换行符?
答案 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\x00
。 readlines
的结果将改为['\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)_read
和os
函数可用模块。虽然它可能让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()