簡體   English   中英

在Windows中讀取多字節文本文件-它如何檢測換行符? (Python 2)

[英]Reading a multibyte text file in Windows - how does it detect newlines? (Python 2)

我以為這是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,然后按“文本模式”打開文件,如下所述: 以二進制和文本模式寫入的文件之間的區別

並將這些行輸入到Python。 但是Windows如何知道該文件是多字節編碼的,以及如何根據問題頂部的注意事項來查找四字節的換行符而不被告知?

  • Windows是否會憑啟發式進行猜測 -因此可能是錯誤的?
  • Unicode的設計是否更加巧妙,從而使Windows換行符樣式在各種編碼中都清晰無誤?
  • 我的理解是錯誤的,並且有一種正確的方法可以處理任何文本文件而無需事先告知編碼?

在這種情況下,結果與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, pq)line = PyString_FromStringAndSize(q, pq) 它幼稚地消耗多達\\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返回一個TextIOWrapperTextIOWrapper通用換行符具有內置支持:

>>> 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不支持此Microsoft擴展到開放mode ,但它確實使os模塊中具有CRT的低I / O(POSIX) _open_read函數。 盡管可能使POSIX程序員感到驚訝,但Microsoft的低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)

在Windows Python中無法直接使用O_WTEXT常量,因為使用os.fdopen以這種模式將文件描述符作為Python file打開並不安全。 CRT期望所有寬字符緩沖區都是wchar_t的大小的倍數,即2的倍數。否則,它會調用無效的參數處理程序來終止進程。 例如(使用cdb調試器):

>>> 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_O_UTF16

首先,首先要在文本模式下以文本形式打開文件,指示正確的encodin。

如果您仍在使用Python 2.7,請使用codecs.open而不是open 在Python 3.x中,只需使用open:

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

而且您應該能夠進行此工作。

其次,likley發生了什么:由於您沒有指定要以二進制模式打開文件,因此Windows以“文本”模式打開文件-Windows確實知道編碼,因此可以找到\\r\\n序列在行中-它會分別讀取行,執行行尾翻譯-使用utf-16-並將這些utf-16字節傳遞給Python。

在Python方面,您可以使用這些值,只需將它們解碼為文本即可:

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

代替

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM