2016-07-15 9 views
0

Ich dachte, dies war ein Vorbehalt einer Unicode-Welt -> Sie können nicht richtig einen Byte-Stream als Schreiben verarbeiten, ohne zu wissen, was die Codierung ist. Wenn Sie eine Kodierung annehmen, erhalten Sie möglicherweise gültige - aber falsche - Zeichen.Lesen einer Multibyte-Textdatei in Windows - wie erkennt es Zeilenumbrüche? (Python 2)

Hier ist ein Test - eine Datei mit dem Schreiben:

hi1 
hi2 

auf der Festplatte mit einer Codierung 2-Byte Unicode gespeichert:

Hex editor view of file

Windows-Zeilenumbrüche sind \r\n als die vier gespeicherten Bytefolge 0D 00 0A 00. Öffnen Sie es in Python 2 mit Standard-Kodierungen, ich glaube, es wird erwartet, ASCII 1-Byte-pro-Zeichen (oder nur einen Strom von Bytes), und es lautet:

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

Es ist nicht decodiert zwei Bytes in einem Zeichen, noch hat die 4-Byte-Zeile Endsequenz als zwei Zeichen erkannt, und die Datei wurde korrekt in zwei Zeilen aufgeteilt.

Vermutlich dann geöffnet Windows die Datei in 'Textmodus', wie hier beschrieben: Difference between files writen in binary and text mode

und fütterte die Zeilen Python. Aber woher wusste Windows, dass die Datei Multibyte-codiert war, und um nach vier Bytes von Zeilenumbrüchen zu suchen, ohne dass ihnen gesagt wurde, wie oben erwähnt?

  • funktioniert Windows erraten, mit einer Heuristik - und kann daher falsch sein?
  • Gibt es mehr Cleverness im Design von Unicode, etwas, das Windows-Newline-Muster über Kodierungen eindeutig macht?
  • Ist mein Verständnis falsch, und es gibt eine korrekte Möglichkeit, jede Textdatei zu verarbeiten, ohne vorher die Kodierung mitgeteilt zu bekommen?
+1

Windows hat kein Konzept von "Textmodus". Sie sprechen über die C-Laufzeit, aber standardmäßig wird der ANSI-Textmodus verwendet, der keine UTF-16-Sequenz "\ r \ x00 \ n \ x00" erkennt. Was Sie tatsächlich sehen, ist die Implementierung der Methode ['file.readlines'] (https://hg.python.org/cpython/file/v2.7.12/Objects/fileobject.c#l1659) in Python 2. Siehe Zeile 1717, 'p = (char *) memchr (Puffer + nfilled, '\ n', nread)' und dann Zeile 1749, 'line = PyString_FromStringAndSize (q, pq)'. Es verbraucht naiv bis zu einem '\ n'-Zeichen, weshalb das eigentliche UTF-16LE' \ n \ x00' geteilt wird. – eryksun

+0

* Windows hat kein Konzept von "Textmodus" * - [fopen() bei MSDN] (https://msdn.microsoft.com/en-us/library/yeby3zcb%28vs.71%29.aspx) - "* Im Textmodus werden Wagenrücklauf-Zeilenvorschub-Kombinationen bei der Eingabe in einzelne Zeilenvorschübe umgesetzt, und Zeilenvorschub-Zeichen werden bei der Ausgabe in Wagenrücklauf-Zeilenvorschub-Kombinationen übersetzt.Wenn eine Unicode-Stream-E/A-Funktion im Textmodus (Standard) ausgeführt wird, Quell- oder Zielstream wird als eine Folge von Multibyte-Zeichen angenommen. * ". Aber es macht Sinn, dass das Unicode-Quad von Python zerschmettert wird, das ist wahrscheinlich meine Antwort. – TessellatingHeckler

+1

Microsofts '_open',' fopen', '_read',' fread' usw. sind alle C-Laufzeitfunktionen. Sie sind nicht von Natur aus Teil von Windows-Datei-I/O, d.h. 'CreateFile',' ReadFile' usw. Die Verwendung von Microsofts CRT ist optional. Sie können eine andere CRT verwenden, die keinen "Textmodus" hat, oder Sie können einfach die Windows-API direkt verwenden. Es kommt vor, dass Windows Python mit MSVC erstellt wird und die CRT-I/O- und Standard-I/O-Laufzeitfunktionen verwendet, um plattformübergreifende Kompatibilität mit POSIX-Betriebssystemen wie Linux und OS X zu ermöglichen. – eryksun

Antwort

1

Das Ergebnis in diesem Fall hat nichts mit dem Windows oder das zu tun Standard-I/O-Implementierung der Microsoft-C-Laufzeit. Wenn Sie dies in Python 2 auf einem Linux-System testen, sehen Sie das gleiche Ergebnis. So funktioniert file.readlines (2.7.12 Source Link) in Python 2. Siehe Zeile 1717, p = (char *)memchr(buffer+nfilled, '\n', nread) und dann Zeile 1749, line = PyString_FromStringAndSize(q, p-q). Es verbraucht naiv bis zu einem \n Zeichen, weshalb die eigentliche UTF-16LE \n\x00 Sequenz aufgeteilt wird.

Wenn Sie die Datei mit Python 2s universellem Zeilenumbruchmodus geöffnet haben, z. open('d:/t/hi2.txt', 'U'), würde die \r\x00 Sequenzen naiv in übersetzt werden. Das Ergebnis von readlines wäre stattdessen ['\xff\xfeh\x00i\x001\x00\n, \x00\n', '\x00h\x00i\x002\x00'].

Also Ihre erste Annahme ist richtig. Sie müssen die Codierung kennen oder zumindest nach einer Unicode-BOM (Byte Order Mark) am Anfang der Datei suchen, wie z. B. \xff\xfe, die UTF-16LE (Little Endian) anzeigt. Zu diesem Zweck empfehle ich das Modul io in Python 2.7, da es die Newline-Übersetzung korrekt behandelt. codecs.open, auf der anderen Seite, erfordert Binärmodus auf der umwickelte Datei und Universal-Newline ignoriert Modus:

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

io.open gibt ein TextIOWrapper, die Unterstützung für die universellen newlines-in gebaut hat:

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

In Bezug auf Microsofts CRT, wird standardmäßig ANSI-Textmodus verwendet. Die ANSI-Codepages von Microsoft sind Obermengen von ASCII, sodass die Zeilenumsetzung des CRT für Dateien funktioniert, die mit einer ASCII-kompatiblen Codierung wie UTF-8 codiert sind. Auf der anderen Seite, ANSI-Text-Modus für eine UTF-16 codierte Datei nicht funktioniert, dh es ist nicht das UTF-16 LE BOM (\xff\xfe) und nicht übersetzt nicht entfernt Zeilenumbrüche:

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

So verwendet Standard-E/A-Textmodus für eine UTF-16-kodierte Datei erfordert das nicht standardmäßige ccs-Flag, z fopen("d:/t/hi2.txt", "rt, ccs=UNICODE"). Python unterstützt diese Microsoft-Erweiterung für das offene mode nicht, aber es stellt die niedrigen E/A-Funktionen (POSIX) des CRT im os Modul zur Verfügung. Während es POSIX-Programmierer überraschen mag, unterstützt die Low-I/O-API von Microsoft auch den Textmodus, einschließlich Unicode. Zum Beispiel:

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

Die O_WTEXT Konstante ist nicht direkt in Windows Python gemacht, weil sie mit diesem Modus einen Dateideskriptor zu öffnen als Python file mit os.fdopen nicht sicher ist. Die CRT erwartet, daß alle Puffer mit breiten Zeichen ein Vielfaches der Grße von wchar_t sind, d. H. Ein Vielfaches von 2. Andernfalls ruft sie den ungültigen Parameterhandler auf, der den Prozeß beendet. Zum Beispiel (mit dem CDB-Debugger):

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

Gleiches gilt für _O_UTF8 und _O_UTF16 gilt.

+0

beginnt Das ist umfassend und schlüssig. Ich hätte bemerken sollen, dass das '\ n \ x00' in zwei Hälften geteilt wurde und etwas dort vermutet wurde. (Nun, ich frage mich, wenn Windows kein Konzept eines Textmodus hat, wie ist es, dass Windows eine feste Textdatei Zeilenende-Sequenz hat - ist es nur eine Konvention, um '\ r \ n' zu verwenden? Oder ist Ihre Aussage" * Die Low-I/O-API von Microsoft unterstützt auch den Textmodus * "eine Änderung seit Ihrem Kommentar?" – TessellatingHeckler

+0

Die CRLF-Konvention von Microsoft wurde von CP/M übernommen, die sie von TOPS-10 geerbt hat. Die Sprachbibliotheken von Microsoft, ob native C/C++ oder .NET-basiert, normalerweise [hard code CRLF] (https://msdn.microsoft.com/en-us/library/system.environment.newline) als die Zeilenendung, aber Ein .NET 'TextWriter' erlaubt die Einstellung [' NewLine'] (https://msdn.microsoft.com/en-us/library/system.io.textwriter.newline). Diese Windows-Konvention ist jedoch im Betriebssystem nicht fest codiert. Die I/O-API von Windows selbst ist binär. Es hat keine Vorstellung von Text "Linien". Viele Programme verwenden die Unix LF-Konvention unter Windows. – eryksun

0

Das Wichtigste zuerst: Öffnen Sie Ihre Datei als Text und geben Sie das korrekte Coding und im expliziten Textmodus an. Wenn Sie immer noch Python 2.7 verwenden, verwenden Sie codecs.open anstelle von open. Verwenden Sie in Python 3.x einfach open:

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

Und Sie sollten daran arbeiten können.

Zweitens, was ist los dort: Da Sie nicht angegeben haben, Sie öffneten die Datei im Binärmodus, öffnen Windows es im "Text" -Modus - Windows weiß über die Codierung und kann daher die \r\n finden Sequenzen in den Zeilen - es liest die Zeilen separat, führt die End-of-Line-Übersetzung - mit UTF-16 - und übergibt diese UTF-16 Bytes an Python.

Auf der Seite Python, können Sie diese Werte verwenden, entschlüsselt werden nur in Text:

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

statt

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

Das ist ein guter Rat, aber es ist nicht relevant für meine Frage, ich frage nicht, wie man mit Unicode-Dateien in Python 2 umgeht. Ich frage, wie die Dateidaten korrekt in zwei Zeilen aufgeteilt werden, sogar mit einer Codierung, die das "unmöglich macht ". - "Windows kennt die Verschlüsselung" - wie? Wie kann es möglicherweise wissen? Wenn es weiß, warum würde Python das nicht wissen? – TessellatingHeckler

+0

Ich habe das auf den Absatz gerichtet, der "Zweite ..." – jsbueno