Écrire dans un fichier UTF-8 en Python

204

Je suis vraiment confus avec le codecs.open function. Quand je fais:

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

Ça me donne l'erreur

UnicodeDecodeError: le codec 'ascii' ne peut pas décoder l'octet 0xef en position 0: l'ordinal n'est pas dans la plage (128)

Si je fais:

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

Ça fonctionne bien.

Question est de savoir pourquoi la première méthode échoue? Et comment insérer la nomenclature?

Si la deuxième méthode est la bonne façon de le faire, quel est l'intérêt d'utiliser codecs.open(filename, "w", "utf-8")?

John Jiang
la source
54
N'utilisez pas de nomenclature en UTF-8. S'il vous plaît.
tchrist
7
@tchrist Huh? Pourquoi pas?
Salman von Abbas
8
@SalmanPK BOM n'est pas nécessaire en UTF-8 et ne fait qu'ajouter de la complexité (par exemple, vous ne pouvez pas simplement concaténer des fichiers BOM et obtenir un texte valide). Voir cette Q&R ; ne manquez pas le gros commentaire sous Q
Alois Mahdal

Réponses:

271

Je crois que le problème est que codecs.BOM_UTF8 c'est une chaîne d'octets, pas une chaîne Unicode. Je soupçonne que le gestionnaire de fichiers essaie de deviner ce que vous voulez vraiment dire en se basant sur "Je suis censé écrire Unicode sous forme de texte encodé en UTF-8, mais vous m'avez donné une chaîne d'octets!"

Essayez d'écrire directement la chaîne Unicode pour la marque d'ordre des octets (c'est-à-dire Unicode U + FEFF), de sorte que le fichier code simplement cela en UTF-8:

import codecs

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

(Cela semble donner la bonne réponse - un fichier avec octets EF BB BF.)

EDIT: La suggestion de S. Lott d'utiliser "utf-8-sig" comme encodage est meilleure que d'écrire explicitement la nomenclature vous-même, mais je vais laisser cette réponse ici car elle explique ce qui n'allait pas auparavant.

Jon Skeet
la source
Attention: ouvrir et ouvrir n'est pas pareil. Si vous faites "à partir de l'importation de codecs ouverte", ce ne sera PAS la même chose que vous tapez simplement "ouvert".
Apache
2
vous pouvez également utiliser codecs.open ('test.txt', 'w', 'utf-8-sig') à la place
bêta fermé
1
Je reçois "TypeError: un entier est requis (type obtenu str)". Je ne comprends pas ce que nous faisons ici. Puis-je avoir une aide s'il vous plait? J'ai besoin d'ajouter une chaîne (paragraphe) à un fichier texte. Dois-je le convertir en entier avant d'écrire?
Mugen
@ Mugen: Le code exact que j'ai écrit fonctionne très bien pour autant que je puisse voir. Je vous suggère de poser une nouvelle question montrant exactement quel code vous avez et où l'erreur se produit.
Jon Skeet
@Mugen vous devez appeler codecs.openau lieu de simplementopen
northben
179

Lisez ce qui suit: http://docs.python.org/library/codecs.html#module-encodings.utf_8_sig

Faites ceci

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

Le fichier résultant est UTF-8 avec la nomenclature attendue.

S.Lott
la source
2
Merci. Cela a fonctionné (Windows 7 x64, Python 2.7.5 x64). Cette solution fonctionne bien lorsque vous ouvrez le fichier en mode "a" (ajouter).
Mohamad Fakih
Cela n'a pas fonctionné pour moi, Python 3 sur Windows. J'ai dû faire cela à la place avec open (file_name, 'wb') en tant que bomfile: bomfile.write (codecs.BOM_UTF8) puis rouvrir le fichier pour l'ajouter.
Dustin Andrews
Peut-être ajouter temp.close()?
user2905353
2
@ user2905353: non requis; ceci est géré par la gestion de contexte de open.
matheburg
11

@ S-Lott donne la bonne procédure, mais en développant les problèmes Unicode , l' interpréteur Python peut fournir plus d'informations.

Jon Skeet a raison (inhabituel) sur le codecsmodule - il contient des chaînes d'octets:

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

Choisir un autre nit, le BOMa un nom Unicode standard , et il peut être entré comme:

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

Il est également accessible via unicodedata:

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

J'utilise la commande file * nix pour convertir un fichier de jeu de caractères inconnu en un fichier utf-8

# -*- 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
la source
1
Utilisez # coding: utf8au lieu de cela # -*- coding: utf-8 -*-qui est beaucoup plus facile à retenir.
show0k