Schreiben Sie in eine UTF-8-Datei in Python

202

Ich bin wirklich verwirrt mit dem codecs.open function. Wenn ich es tue:

file = codecs.open("temp", "w", "utf-8")
file.write(codecs.BOM_UTF8)
file.close()

Es gibt mir den Fehler

UnicodeDecodeError: Der Codec 'ascii' kann das Byte 0xef an Position 0 nicht dekodieren: Ordnungszahl nicht im Bereich (128)

Wenn ich mache:

file = open("temp", "w")
file.write(codecs.BOM_UTF8)
file.close()

Es funktioniert gut.

Die Frage ist, warum die erste Methode fehlschlägt. Und wie füge ich die Bombe ein?

Wenn die zweite Methode die richtige ist, wozu dann codecs.open(filename, "w", "utf-8")?

John Jiang
quelle
54
Verwenden Sie in UTF-8 keine Stückliste. Bitte.
Tchrist
7
@ tchrist Huh? Warum nicht?
Salman von Abbas
8
@ SalmanPK-Stückliste wird in UTF-8 nicht benötigt und erhöht nur die Komplexität (z. B. können Sie Stücklistendateien nicht einfach verketten und das Ergebnis mit gültigem Text versehen). Siehe diese Fragen und Antworten ; Verpassen Sie nicht den großen Kommentar unter Q
Alois Mahdal

Antworten:

270

Ich glaube, das Problem ist, dass codecs.BOM_UTF8es sich um eine Byte-Zeichenfolge handelt, nicht um eine Unicode-Zeichenfolge. Ich vermute, der Datei-Handler versucht zu erraten, was Sie wirklich meinen, basierend auf "Ich soll Unicode als UTF-8-codierten Text schreiben, aber Sie haben mir eine Byte-Zeichenfolge gegeben!"

Versuchen Sie, die Unicode-Zeichenfolge für die Bytereihenfolge (dh Unicode U + FEFF) direkt zu schreiben, sodass die Datei diese nur als UTF-8 codiert:

import codecs

file = codecs.open("lol", "w", "utf-8")
file.write(u'\ufeff')
file.close()

(Das scheint die richtige Antwort zu geben - eine Datei mit Bytes EF BB BF.)

EDIT: S. Lotts Vorschlag , "utf-8-sig" als Codierung zu verwenden, ist besser als das explizite Schreiben der Stückliste selbst, aber ich werde diese Antwort hier belassen, da sie erklärt, was vorher falsch gelaufen ist.

Jon Skeet
quelle
Warnung: offen und offen ist nicht dasselbe. Wenn Sie "from codecs import open" ausführen, ist dies NICHT dasselbe, als würden Sie einfach "open" eingeben.
Apache
2
Sie können stattdessen auch codecs.open ('test.txt', 'w', 'utf-8-sig') verwenden
Beta-geschlossen
1
Ich erhalte "TypeError: Eine Ganzzahl ist erforderlich (habe Typ str)". Ich verstehe nicht, was wir hier machen. Kann mir bitte jemand helfen? Ich muss eine Zeichenfolge (Absatz) an eine Textdatei anhängen. Muss ich das zuerst in eine Ganzzahl umwandeln, bevor ich schreibe?
Mugen
@Mugen: Der genaue Code, den ich geschrieben habe, funktioniert soweit ich sehen kann einwandfrei . Ich schlage vor, Sie stellen eine neue Frage, die genau zeigt , welchen Code Sie haben und wo der Fehler auftritt.
Jon Skeet
@Mugen müssen Sie anrufen, codecs.openanstatt nuropen
Northben
178

Lesen Sie Folgendes: http://docs.python.org/library/codecs.html#module-encodings.utf_8_sig

Mach das

with codecs.open("test_output", "w", "utf-8-sig") as temp:
    temp.write("hi mom\n")
    temp.write(u"This has ♭")

Die resultierende Datei ist UTF-8 mit der erwarteten Stückliste.

S.Lott
quelle
2
Vielen Dank. Das hat funktioniert (Windows 7 x64, Python 2.7.5 x64). Diese Lösung funktioniert gut, wenn Sie die Datei im Modus "a" (Anhängen) öffnen.
Mohamad Fakih
Das hat bei mir nicht funktioniert, Python 3 unter Windows. Ich musste dies stattdessen mit open (Dateiname, 'wb') als bomfile: bomfile.write (codecs.BOM_UTF8) tun und dann die Datei zum Anhängen erneut öffnen.
Dustin Andrews
Vielleicht hinzufügen temp.close()?
user2905353
2
@ user2905353: nicht erforderlich; Dies wird durch die Kontextverwaltung von erledigt open.
Matheburg
11

@ S-Lott bietet die richtige Vorgehensweise, aber der Python- Interpreter erweitert die Unicode- Probleme und bietet weitere Einblicke.

Jon Skeet hat Recht (ungewöhnlich) mit dem codecsModul - es enthält Byte-Strings:

>>> import codecs
>>> codecs.BOM
'\xff\xfe'
>>> codecs.BOM_UTF8
'\xef\xbb\xbf'
>>> 

Wenn Sie eine andere Nit auswählen, BOMhat der einen Standard- Unicode- Namen und kann wie folgt eingegeben werden:

>>> bom= u"\N{ZERO WIDTH NO-BREAK SPACE}"
>>> bom
u'\ufeff'

Es ist auch zugänglich über unicodedata:

>>> import unicodedata
>>> unicodedata.lookup('ZERO WIDTH NO-BREAK SPACE')
u'\ufeff'
>>> 
gimel
quelle
8

Ich verwende den Befehl file * nix, um eine unbekannte Zeichensatzdatei in eine utf-8-Datei zu konvertieren

# -*- 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
1
Verwenden Sie # coding: utf8stattdessen # -*- coding: utf-8 -*-ist viel einfacher zu merken.
Show0k