Unicode (UTF-8) Lesen und Schreiben in Dateien in Python

328

Ich habe einige Gehirnfehler beim Verstehen des Lesens und Schreibens von Text in eine Datei (Python 2.4).

# The string, which has an a-acute in it.
ss = u'Capit\xe1n'
ss8 = ss.encode('utf8')
repr(ss), repr(ss8)

("u'Capit \ xe1n", "Capit \ xc3 \ xa1n")

print ss, ss8
print >> open('f1','w'), ss8

>>> file('f1').read()
'Capit\xc3\xa1n\n'

Also tippe ich Capit\xc3\xa1nin meinen Lieblingseditor in Datei f2.

Dann:

>>> open('f1').read()
'Capit\xc3\xa1n\n'
>>> open('f2').read()
'Capit\\xc3\\xa1n\n'
>>> open('f1').read().decode('utf8')
u'Capit\xe1n\n'
>>> open('f2').read().decode('utf8')
u'Capit\\xc3\\xa1n\n'

Was verstehe ich hier nicht? Offensichtlich fehlt mir ein wichtiges Stück Magie (oder ein gesunder Menschenverstand). Was gibt man in Textdateien ein, um korrekte Konvertierungen zu erzielen?

Was ich hier wirklich nicht verstehe, ist der Sinn der UTF-8-Darstellung, wenn Sie Python nicht dazu bringen können, es zu erkennen, wenn es von außen kommt. Vielleicht sollte ich einfach JSON die Zeichenfolge ausgeben und diese stattdessen verwenden, da dies eine aufstellbare Darstellung hat! Gibt es eine ASCII-Darstellung dieses Unicode-Objekts, die Python erkennt und dekodiert, wenn es aus einer Datei eingeht? Wenn ja, wie bekomme ich es?

>>> print simplejson.dumps(ss)
'"Capit\u00e1n"'
>>> print >> file('f3','w'), simplejson.dumps(ss)
>>> simplejson.load(open('f3'))
u'Capit\xe1n'
Gregg Lind
quelle

Antworten:

110

In der Notation

u'Capit\xe1n\n'

Das "\ xe1" repräsentiert nur ein Byte. "\ x" sagt Ihnen, dass "e1" hexadezimal ist. Wenn du schreibst

Capit\xc3\xa1n

In Ihrer Datei befindet sich "\ xc3". Das sind 4 Bytes und in Ihrem Code lesen Sie sie alle. Sie können dies sehen, wenn Sie sie anzeigen:

>>> open('f2').read()
'Capit\\xc3\\xa1n\n'

Sie können sehen, dass der Backslash durch einen Backslash maskiert wird. Sie haben also vier Bytes in Ihrer Zeichenfolge: "\", "x", "c" und "3".

Bearbeiten:

Wie andere in ihren Antworten betonten, sollten Sie nur die Zeichen in den Editor eingeben und Ihr Editor sollte dann die Konvertierung in UTF-8 durchführen und speichern.

Wenn Sie tatsächlich eine Zeichenfolge in diesem Format haben, können Sie sie mit dem string_escapeCodec in eine normale Zeichenfolge dekodieren:

In [15]: print 'Capit\\xc3\\xa1n\n'.decode('string_escape')
Capitán

Das Ergebnis ist eine Zeichenfolge, die in UTF-8 codiert ist, wobei das Zeichen mit Akzent durch die beiden Bytes dargestellt wird, die \\xc3\\xa1in die ursprüngliche Zeichenfolge geschrieben wurden. Wenn Sie eine Unicode-Zeichenfolge haben möchten, müssen Sie diese erneut mit UTF-8 dekodieren.

Zu Ihrer Bearbeitung: Sie haben kein UTF-8 in Ihrer Datei. Um tatsächlich zu sehen, wie es aussehen würde:

s = u'Capit\xe1n\n'
sutf8 = s.encode('UTF-8')
open('utf-8.out', 'w').write(sutf8)

Vergleichen Sie den Inhalt der Datei utf-8.outmit dem Inhalt der Datei, die Sie mit Ihrem Editor gespeichert haben.


quelle
Was bringt das utf-8-codierte Format, wenn Python damit Dateien einlesen kann? Mit anderen Worten, gibt es eine ASCII-Darstellung, die Python in \ xc3 als 1 Byte liest?
Gregg Lind
4
Die Antwort auf Ihre Frage "Also, was ist der Sinn ..." lautet "Mu". (da Python in UTF-8 codierte Dateien lesen kann). Für Ihre zweite Frage: \ xc3 ist nicht Teil des ASCII-Satzes. Vielleicht meinen Sie stattdessen "8-Bit-Codierung". Sie sind verwirrt über Unicode und Codierungen. Es ist in Ordnung, viele sind es.
Zot
8
Versuchen Sie dies als Grundierung zu lesen
tzot
Hinweis: u'\xe1'Dies ist ein Unicode-Codepunkt U+00e1, der je nach Zeichenkodierung mit 1 oder mehr Bytes dargestellt werden kann (in utf-8 sind es 2 Bytes). b'\xe1'ist ein Byte (eine Zahl 225), welcher Buchstabe, falls vorhanden, von der Zeichencodierung abhängt, die zum Decodieren verwendet wird, z. B. б( U+0431) in cp1251, с( U+0441) in cp866 usw.
jfs
11
Es ist erstaunlich, wie viele britische Programmierer sagen "benutze nur ASCII" und dann nicht erkennen, dass das £ -Zeichen es nicht ist. Die meisten wissen nicht, dass ascii! = Lokale Codepage (dh latin1).
Danny Staple
710

Anstatt mit den Codierungs- und Decodierungsmethoden herumzuspielen, fällt es mir leichter, die Codierung beim Öffnen der Datei anzugeben. Das in Python 2.6 hinzugefügte ioModul bietet eine io.openFunktion mit einem Codierungsparameter.

Verwenden Sie die Methode open aus dem ioModul.

>>>import io
>>>f = io.open("test", mode="r", encoding="utf-8")

Nach dem Aufruf der Funktion read () von f wird dann ein codiertes Unicode-Objekt zurückgegeben.

>>>f.read()
u'Capit\xe1l\n\n'

Beachten Sie, dass die io.openFunktion in Python 3 ein Alias ​​für die integrierte openFunktion ist. Die integrierte Öffnungsfunktion unterstützt nur das Codierungsargument in Python 3, nicht in Python 2.

Bearbeiten: Zuvor empfahl diese Antwort das Codecs- Modul. Die Codecs Modul kann zu Problemen führen , wenn das Mischen read()undreadline() , so dass diese Antwort empfiehlt nun das io - Modul statt.

Verwenden Sie die Methode open aus dem Codecs-Modul.

>>>import codecs
>>>f = codecs.open("test", "r", "utf-8")

Nach dem Aufruf der Funktion read () von f wird dann ein codiertes Unicode-Objekt zurückgegeben.

>>>f.read()
u'Capit\xe1l\n\n'

Wenn Sie die Codierung einer Datei kennen, ist die Verwendung des Codecs-Pakets weniger verwirrend.

Sehen http://docs.python.org/library/codecs.html#codecs.open

Tim Swast
quelle
74
Funktioniert perfekt für Dateien zu, statt zu schreiben open(file,'w')tun codecs.open(file,'w','utf-8')gelöst
Matt Connolly
1
Dies ist die Antwort, nach der ich gesucht habe :)
Justin
6
Entspricht die codecs.open(...)Methode auch vollständig dem with open(...):Stil, bei dem es withdarum geht, die Datei zu schließen, nachdem alles erledigt ist? Es scheint sowieso zu funktionieren.
versuchen-fangen-endlich
2
@ try-catch-finally Ja. Ich benutze die with codecs.open(...) as f:ganze Zeit.
Tim Swast
6
Ich wünschte, ich könnte dies hundertmal positiv bewerten. Diese Antwort ist wie Wasser in einer Wüste, nachdem sie sich mehrere Tage lang mit Codierungsproblemen befasst hat, die durch viele gemischte Daten verursacht wurden, und mit gekreuzten Augen über das Codieren gelesen hat. Ich wünschte, ich hätte es früher gesehen.
Mike Girard
45

Jetzt brauchen Sie nur noch Python3 open(Filename, 'r', encoding='utf-8')

[Bearbeiten am 10.02.2016 zur angeforderten Klarstellung]

Python3 hat den Codierungsparameter zu seiner offenen Funktion hinzugefügt . Die folgenden Informationen zur Öffnungsfunktion finden Sie hier: https://docs.python.org/3/library/functions.html#open

open(file, mode='r', buffering=-1, 
      encoding=None, errors=None, newline=None, 
      closefd=True, opener=None)

Codierung ist der Name der Codierung, die zum Decodieren oder Codieren der Datei verwendet wird. Dies sollte nur im Textmodus verwendet werden. Die Standardcodierung ist plattformabhängig (unabhängig davon, was locale.getpreferredencoding () zurückgibt), es kann jedoch jede von Python unterstützte Textcodierung verwendet werden. Im Codecs- Modul finden Sie eine Liste der unterstützten Codierungen.

Durch Hinzufügen encoding='utf-8'als Parameter zur Öffnungsfunktion erfolgt das Lesen und Schreiben der Datei als utf8 (dies ist jetzt auch die Standardcodierung für alles, was in Python ausgeführt wird).

Dakusan
quelle
Könnten Sie bitte Ihre Antwort näher erläutern und etwas mehr Beschreibung der von Ihnen bereitgestellten Lösung hinzufügen?
Abarisone
2
Es sieht so aus, als codecs.open('somefile', encoding='utf-8') ob
Taylor Edmiston
18

Also habe ich eine Lösung für das gefunden, wonach ich suche:

print open('f2').read().decode('string-escape').decode("utf-8")

Es gibt einige ungewöhnliche Codecs, die hier nützlich sind. Diese spezielle Lesart ermöglicht es, UTF-8-Darstellungen aus Python heraus zu übernehmen, sie in eine ASCII-Datei zu kopieren und sie in Unicode einlesen zu lassen. Bei der Dekodierung "String-Escape" werden die Schrägstriche nicht verdoppelt.

Dies ermöglicht die Art von Rundreise, die ich mir vorgestellt habe.

Gregg Lind
quelle
1
Gute Resonanz, ich habe beide Lösungen getestet (codecs.open(file,"r","utf-8")und einfach open(file,"r").read().decode("utf-8")und beide haben perfekt funktioniert.
Eagle
Ich erhalte die Meldung "TypeError: erwartetes str-, byte- oder os.PathLike-Objekt, nicht _io.TextIOWrapper". Warum?
JinSnow
Ich denke, angesichts der Anzahl der Upvotes wäre es eine großartige Idee, die zweite Antwort zu akzeptieren :)
Jacquot
14
# -*- encoding: utf-8 -*-

# converting a unknown formatting file in utf-8

import codecs
import commands

file_location = "jumper.sub"
file_encoding = commands.getoutput('file -b --mime-encoding %s' % file_location)

file_stream = codecs.open(file_location, 'r', file_encoding)
file_output = codecs.open(file_location+"b", 'w', 'utf-8')

for l in file_stream:
    file_output.write(l)

file_stream.close()
file_output.close()
Ricardo
quelle
14

Tatsächlich funktionierte dies für mich beim Lesen einer Datei mit UTF-8-Codierung in Python 3.2:

import codecs
f = codecs.open('file_name.txt', 'r', 'UTF-8')
for line in f:
    print(line)
Sina
quelle
6

Um eine Unicode-Zeichenfolge einzulesen und dann an HTML zu senden, habe ich Folgendes getan:

fileline.decode("utf-8").encode('ascii', 'xmlcharrefreplace')

Nützlich für http-Server mit Python-Unterstützung.

praj
quelle
6

Sie sind über das allgemeine Problem mit Codierungen gestolpert: Wie kann ich feststellen, in welcher Codierung eine Datei enthalten ist?

Antwort: Dies ist nur möglich, wenn das Dateiformat dies vorsieht. XML beginnt beispielsweise mit:

<?xml encoding="utf-8"?>

Dieser Header wurde sorgfältig ausgewählt, damit er unabhängig von der Codierung gelesen werden kann. In Ihrem Fall gibt es keinen solchen Hinweis, daher haben weder Ihr Editor noch Python eine Ahnung, was los ist. Daher müssen Sie das codecsModul verwenden und verwendencodecs.open(path,mode,encoding) , das das fehlende Bit in Python bereitstellt.

Bei Ihrem Editor müssen Sie prüfen, ob er eine Möglichkeit zum Festlegen der Codierung einer Datei bietet.

Der Zweck von UTF-8 besteht darin, 21-Bit-Zeichen (Unicode) als 8-Bit-Datenstrom codieren zu können (da dies das einzige ist, was alle Computer auf der Welt verarbeiten können). Da die meisten Betriebssysteme jedoch älter sind als die Unicode-Ära, verfügen sie nicht über geeignete Tools, um die Codierungsinformationen an Dateien auf der Festplatte anzuhängen.

Das nächste Problem ist die Darstellung in Python. Dies wird im Kommentar von heikogerlach perfekt erklärt . Sie müssen verstehen, dass Ihre Konsole nur ASCII anzeigen kann. Um Unicode oder irgendetwas> = Zeichencode 128 anzuzeigen, muss ein Escape-Mittel verwendet werden. In Ihrem Editor dürfen Sie nicht die maskierte Anzeigezeichenfolge eingeben, sondern die Bedeutung der Zeichenfolge (in diesem Fall müssen Sie den Umlaut eingeben und die Datei speichern).

Sie können jedoch die Python-Funktion eval () verwenden, um eine maskierte Zeichenfolge in eine Zeichenfolge umzuwandeln:

>>> x = eval("'Capit\\xc3\\xa1n\\n'")
>>> x
'Capit\xc3\xa1n\n'
>>> x[5]
'\xc3'
>>> len(x[5])
1

Wie Sie sehen können, wurde die Zeichenfolge "\ xc3" in ein einzelnes Zeichen umgewandelt. Dies ist jetzt eine 8-Bit-Zeichenfolge, UTF-8-codiert. So erhalten Sie Unicode:

>>> x.decode('utf-8')
u'Capit\xe1n\n'

Gregg Lind fragte: Ich denke, hier fehlen einige Teile: Die Datei f2 enthält: hex:

0000000: 4361 7069 745c 7863 335c 7861 316e  Capit\xc3\xa1n

codecs.open('f2','rb', 'utf-8')Liest sie beispielsweise alle in separaten Zeichen (erwartet). Gibt es eine Möglichkeit, in eine Datei in ASCII zu schreiben, die funktionieren würde?

Antwort: Das hängt davon ab, was Sie meinen. ASCII kann keine Zeichen> 127 darstellen. Sie müssen also sagen, dass die nächsten Zeichen etwas Besonderes bedeuten, wie es die Sequenz "\ x" tut. Es heißt: Die nächsten beiden Zeichen sind der Code eines einzelnen Zeichens. "\ u" macht dasselbe mit vier Zeichen, um Unicode bis zu 0xFFFF (65535) zu codieren.

Sie können Unicode also nicht direkt in ASCII schreiben (da ASCII einfach nicht dieselben Zeichen enthält). Sie können es als Escapezeichenfolge schreiben (wie in f2). In diesem Fall kann die Datei als ASCII dargestellt werden. Oder Sie können es als UTF-8 schreiben. In diesem Fall benötigen Sie einen sicheren 8-Bit-Stream.

Ihre Lösung decode('string-escape')funktioniert, aber Sie müssen wissen, wie viel Speicher Sie verwenden: Dreifache Menge an Speicher codecs.open().

Denken Sie daran, dass eine Datei nur eine Folge von Bytes mit 8 Bits ist. Weder die Bits noch die Bytes haben eine Bedeutung. Sie sagen "65 bedeutet 'A'". Da \xc3\xa1"à" werden sollte, der Computer jedoch keine Mittel zum Wissen hat, müssen Sie dies durch Angabe der Codierung angeben, die beim Schreiben der Datei verwendet wurde.

Aaron Digulla
quelle
Ich denke, hier fehlen einige Teile: Die Datei f2 enthält: hex: 0000000: 4361 7069 745c 7863 335c 7861 316e 0a Capit \ xc3 \ xa1n. codecs.open ('f2', 'rb', 'utf-8') liest sie beispielsweise alle in separaten Zeichen (erwartet). Gibt es eine Möglichkeit, in eine Datei in ASCII zu schreiben, die funktionieren würde?
Gregg Lind
6

mit Ausnahme von codecs.open()kann io.open()man mit Python2 oder Python3 arbeiten, um Unicode-Dateien zu lesen / schreiben

Beispiel

import io

text = u'á'
encoding = 'utf8'

with io.open('data.txt', 'w', encoding=encoding, newline='\n') as fout:
    fout.write(text)

with io.open('data.txt', 'r', encoding=encoding, newline='\n') as fin:
    text2 = fin.read()

assert text == text2
Ryan
quelle
1
+1 io ist viel besser als Codecs.
personal_cloud
Ja, die Verwendung von io ist besser. Aber ich habe die with-Anweisung so geschrieben with io.open('data.txt', 'w', 'utf-8') as file:und einen Fehler bekommen : TypeError: an integer is required. Nachdem ich zu gewechselt habe with io.open('data.txt', 'w', encoding='utf-8') as file:und es funktioniert hat.
Evan Hu
5

Nun, Ihr bevorzugter Texteditor erkennt nicht, dass \xc3\xa1es sich um Zeichenliterale handeln soll, interpretiert sie jedoch als Text. Deshalb erhalten Sie in der letzten Zeile die doppelten Backslashes - es ist jetzt ein echter Backslash + xc3usw. in Ihrer Datei.

Wenn Sie codierte Dateien in Python lesen und schreiben möchten, verwenden Sie am besten das Codecs- Modul.

Das Einfügen von Text zwischen dem Terminal und den Anwendungen ist schwierig, da Sie nicht wissen, welches Programm Ihren Text mit welcher Codierung interpretiert. Sie könnten Folgendes versuchen:

>>> s = file("f1").read()
>>> print unicode(s, "Latin-1")
Capitán

Fügen Sie diese Zeichenfolge dann in Ihren Editor ein und stellen Sie sicher, dass sie mit Latin-1 gespeichert wird. Unter der Annahme, dass die Zwischenablage die Zeichenfolge nicht verstümmelt, sollte der Roundtrip funktionieren.

Torsten Marek
quelle
4

Die \ x .. -Sequenz ist etwas, das für Python spezifisch ist. Es ist keine universelle Byte-Escape-Sequenz.

Wie Sie UTF-8-codiertes Nicht-ASCII tatsächlich eingeben, hängt von Ihrem Betriebssystem und / oder Ihrem Editor ab. So geht's in Windows . Damit OS X ein mit einem akuten Akzent eingibt, können Sie einfach option+ Edrücken A, und fast alle Texteditoren in OS X unterstützen UTF-8.

ʞɔıu
quelle
3

Sie können die ursprüngliche open()Funktion auch für die Arbeit mit Unicode-Dateien verbessern, indem Sie sie mithilfe der partialFunktion ersetzen . Das Schöne an dieser Lösung ist, dass Sie keinen alten Code ändern müssen. Es ist transparent.

import codecs
import functools
open = functools.partial(codecs.open, encoding='utf-8')
Hipertracker
quelle
1

Ich habe versucht, iCal mit Python 2.7.9 zu analysieren :

aus icalendar Kalender importieren

Aber ich bekam:

 Traceback (most recent call last):
 File "ical.py", line 92, in parse
    print "{}".format(e[attr])
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe1' in position 7: ordinal not in range(128)

und es wurde mit nur behoben:

print "{}".format(e[attr].encode("utf-8"))

(Jetzt kann es liké á böss drucken.)

Alexx Roche
quelle
0

Ich fand den einfachsten Ansatz, indem ich die Standardcodierung des gesamten Skripts in 'UTF-8' änderte:

import sys
reload(sys)
sys.setdefaultencoding('utf8')

jeder open, printoder andere Aussage wird nur verwenden utf8.

Funktioniert zumindest für Python 2.7.9.

Thx geht zu https://markhneedham.com/blog/2015/05/21/python-unicodeencodeerror-ascii-codec-cant-encode-character-uxfc-in-position-11-ordinal-not-in-range128/ ( schau dir das Ende an).

dr0i
quelle