Lecture et écriture Unicode (UTF-8) dans des fichiers en Python

331

J'ai des problèmes de cerveau dans la compréhension de la lecture et de l'écriture de texte dans un fichier (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'

Je tape donc Capit\xc3\xa1ndans mon éditeur préféré, dans le fichier f2.

Ensuite:

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

Qu'est-ce que je ne comprends pas ici? De toute évidence, il me manque un peu de magie (ou de bon sens). Que faut-il taper dans des fichiers texte pour obtenir des conversions appropriées?

Ce que je ne parviens vraiment pas à comprendre ici, c'est à quoi sert la représentation UTF-8, si vous ne pouvez pas vraiment faire reconnaître Python, quand il vient de l'extérieur. Peut-être que je devrais simplement JSON vider la chaîne, et l'utiliser à la place, car cela a une représentation vérifiable! Plus précisément, existe-t-il une représentation ASCII de cet objet Unicode que Python reconnaîtra et décodera, en arrivant d'un fichier? Si oui, comment puis-je l'obtenir?

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

Réponses:

110

Dans la notation

u'Capit\xe1n\n'

le "\ xe1" ne représente qu'un octet. "\ x" vous indique que "e1" est en hexadécimal. Quand tu écris

Capit\xc3\xa1n

dans votre fichier, vous avez "\ xc3". Ce sont 4 octets et dans votre code vous les lisez tous. Vous pouvez le voir lorsque vous les affichez:

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

Vous pouvez voir que la barre oblique inverse est échappée par une barre oblique inverse. Vous avez donc quatre octets dans votre chaîne: "\", "x", "c" et "3".

Éditer:

Comme d'autres l'ont souligné dans leurs réponses, vous devez simplement saisir les caractères dans l'éditeur et votre éditeur doit ensuite gérer la conversion en UTF-8 et l'enregistrer.

Si vous avez réellement une chaîne dans ce format, vous pouvez utiliser le string_escapecodec pour la décoder en une chaîne normale:

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

Le résultat est une chaîne codée en UTF-8 où le caractère accentué est représenté par les deux octets qui ont été écrits \\xc3\\xa1dans la chaîne d'origine. Si vous voulez avoir une chaîne unicode, vous devez décoder à nouveau avec UTF-8.

Pour votre édition: vous n'avez pas UTF-8 dans votre fichier. Pour voir à quoi cela ressemblerait:

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

Comparez le contenu du fichier utf-8.outau contenu du fichier que vous avez enregistré avec votre éditeur.


la source
Alors, quel est l'intérêt du format encodé utf-8 si python peut lire dans des fichiers l'utilisant? En d'autres termes, existe-t-il une représentation ascii que python lira dans \ xc3 comme 1 octet?
Gregg Lind
4
La réponse à votre question «Alors, quel est le point…» est «Mu». (puisque Python peut lire les fichiers encodés en UTF-8). Pour votre deuxième question: \ xc3 ne fait pas partie de l'ensemble ASCII. Peut-être voulez-vous plutôt dire "codage 8 bits". Vous êtes confus au sujet d'Unicode et des encodages; c'est ok, beaucoup le sont.
tzot
8
Essayez de lire ceci comme amorce: joelonsoftware.com/articles/Unicode.html
tzot
note: u'\xe1'est un point de code Unicode U+00e1qui peut être représenté en utilisant 1 ou plusieurs octets selon le codage des caractères (il est de 2 octets dans utf-8). b'\xe1'est un octet (un nombre 225), quelle lettre s'il peut représenter dépend du codage de caractères utilisé pour le décoder, par exemple, il est б( U+0431) dans cp1251, с( U+0441) dans cp866, etc.
jfs
11
Il est étonnant de voir combien de codeurs britanniques disent "utilisez simplement ascii" et ne réalisent pas que le signe £ ne l'est pas. La plupart ne savent pas que ascii! = Page de codes locale (c'est-à-dire latin1).
Danny Staple
713

Plutôt que de jouer avec les méthodes d'encodage et de décodage, je trouve plus facile de spécifier l'encodage lors de l'ouverture du fichier. Le iomodule (ajouté en Python 2.6) fournit une io.openfonction, qui a un paramètre de codage.

Utilisez la méthode ouverte du iomodule.

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

Ensuite, après avoir appelé la fonction read () de f, un objet Unicode codé est renvoyé.

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

Notez que dans Python 3, la io.openfonction est un alias pour la openfonction intégrée. La fonction ouverte intégrée ne prend en charge que l'argument de codage en Python 3, pas Python 2.

Edit: Auparavant, cette réponse recommandait le module codecs . Le module codecs peut provoquer des problèmes lors du mixage read()etreadline() , donc cette réponse recommande désormais le module io à la place.

Utilisez la méthode ouverte du module codecs.

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

Ensuite, après avoir appelé la fonction read () de f, un objet Unicode codé est renvoyé.

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

Si vous connaissez l'encodage d'un fichier, l'utilisation du paquet de codecs va être beaucoup moins déroutante.

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

Tim Swast
la source
74
Fonctionne parfaitement pour l'écriture de fichiers aussi, au lieu de open(file,'w')le codecs.open(file,'w','utf-8')résoudre
Matt Connolly
1
C'est la réponse que je cherchais :)
Justin
6
La codecs.open(...)méthode est-elle également parfaitement conforme au with open(...):style, où la withfermeture du fichier se soucie après tout? Cela semble fonctionner de toute façon.
try-catch-finally
2
@ try-catch-finally Oui. J'utilise with codecs.open(...) as f:tout le temps.
Tim Swast
6
J'aimerais pouvoir voter contre cela cent fois. Après avoir agonisé pendant plusieurs jours à cause de problèmes de codage causés par un grand nombre de données mitigées et après avoir lu les yeux croisés sur le codage, cette réponse est comme l'eau dans un désert. J'aurais aimé l'avoir vu plus tôt.
Mike Girard
46

Maintenant, tout ce dont vous avez besoin en Python3 est open(Filename, 'r', encoding='utf-8')

[Modifier le 2016-02-10 pour obtenir les clarifications demandées]

Python3 a ajouté le paramètre d' encodage à sa fonction ouverte. Les informations suivantes sur la fonction ouverte sont collectées à partir d'ici: 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)

L'encodage est le nom de l'encodage utilisé pour décoder ou encoder le fichier. Cela ne doit être utilisé qu'en mode texte. L'encodage par défaut dépend de la plateforme (quel que soit le retour de locale.getpreferredencoding () ), mais tout encodage de texte pris en charge par Python peut être utilisé. Voir le module des codecs pour la liste des encodages pris en charge.

Ainsi, en ajoutant encoding='utf-8'un paramètre à la fonction ouverte, la lecture et l'écriture du fichier se font toutes en utf8 (qui est également maintenant l'encodage par défaut de tout ce qui se fait en Python.)

Dakusan
la source
Pourriez-vous élaborer davantage votre réponse en ajoutant un peu plus de description sur la solution que vous proposez?
abarisone
2
Il semble que cela soit disponible en python 2 en utilisant le module codecs - codecs.open('somefile', encoding='utf-8') stackoverflow.com/a/147756/149428
Taylor Edmiston
18

J'ai donc trouvé une solution pour ce que je recherche, qui est:

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

Il existe des codecs inhabituels qui sont utiles ici. Cette lecture particulière permet de prendre des représentations UTF-8 depuis Python, de les copier dans un fichier ASCII et de les lire dans Unicode. Sous le décodage "string-escape", les barres obliques ne seront pas doublées.

Cela permet le type de voyage aller-retour que j'imaginais.

Gregg Lind
la source
1
Bonne réponse, j'ai testé les deux solutions (codecs.open(file,"r","utf-8")et simplement open(file,"r").read().decode("utf-8")et les deux ont parfaitement fonctionné.
Eagle
Je reçois un "TypeError: objet attendu str, octets ou os.PathLike, pas _io.TextIOWrapper" une idée pourquoi?
JinSnow
Je pense, compte tenu du nombre de votes positifs, que ce serait une bonne idée d'accepter la deuxième réponse :)
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
la source
14

En fait, cela a fonctionné pour moi pour lire un fichier avec l'encodage UTF-8 en Python 3.2:

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

Pour lire dans une chaîne Unicode puis envoyer au HTML, j'ai fait ceci:

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

Utile pour les serveurs http propulsés par python.

praj
la source
6

Vous êtes tombé sur le problème général des encodages: comment savoir dans quel encodage se trouve un fichier?

Réponse: Vous ne pouvez pas sauf si le format de fichier le prévoit. XML, par exemple, commence par:

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

Cet en-tête a été soigneusement choisi afin qu'il puisse être lu quel que soit l'encodage. Dans votre cas, il n'y a pas un tel indice, donc ni votre éditeur ni Python n'ont la moindre idée de ce qui se passe. Par conséquent, vous devez utiliser le codecsmodule et utiliser celui codecs.open(path,mode,encoding)qui fournit le bit manquant en Python.

Quant à votre éditeur, vous devez vérifier s'il offre un moyen de définir l'encodage d'un fichier.

L'utilité de l'UTF-8 est de pouvoir encoder des caractères 21 bits (Unicode) en tant que flux de données 8 bits (car c'est la seule chose que tous les ordinateurs du monde peuvent gérer). Mais comme la plupart des systèmes d'exploitation sont antérieurs à l'ère Unicode, ils ne disposent pas d'outils appropriés pour attacher les informations d'encodage aux fichiers sur le disque dur.

Le problème suivant est la représentation en Python. Ceci est parfaitement expliqué dans le commentaire de heikogerlach . Vous devez comprendre que votre console ne peut afficher que ASCII. Pour afficher Unicode ou quoi que ce soit> = charcode 128, il doit utiliser un moyen de s'échapper. Dans votre éditeur, vous ne devez pas taper la chaîne d'affichage échappée mais ce que signifie la chaîne (dans ce cas, vous devez saisir le tréma et enregistrer le fichier).

Cela dit, vous pouvez utiliser la fonction Python eval () pour transformer une chaîne échappée en chaîne:

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

Comme vous pouvez le voir, la chaîne "\ xc3" a été transformée en un seul caractère. Il s'agit maintenant d'une chaîne de 8 bits, encodée en UTF-8. Pour obtenir Unicode:

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

Gregg Lind a demandé: Je pense qu'il manque des morceaux ici: le fichier f2 contient: hex:

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

codecs.open('f2','rb', 'utf-8'), par exemple, les lit tous dans des caractères séparés (attendu) Existe-t-il un moyen d'écrire dans un fichier en ASCII qui fonctionnerait?

Réponse: Cela dépend de ce que vous voulez dire. ASCII ne peut pas représenter des caractères> 127. Il vous faut donc un moyen de dire "les prochains caractères signifient quelque chose de spécial", ce que fait la séquence "\ x". Il dit: Les deux caractères suivants sont le code d'un seul caractère. "\ u" fait de même en utilisant quatre caractères pour encoder Unicode jusqu'à 0xFFFF (65535).

Vous ne pouvez donc pas écrire directement Unicode en ASCII (car ASCII ne contient tout simplement pas les mêmes caractères). Vous pouvez l'écrire comme des échappements de chaîne (comme dans f2); dans ce cas, le fichier peut être représenté en ASCII. Ou vous pouvez l'écrire en UTF-8, auquel cas, vous avez besoin d'un flux sécurisé 8 bits.

Votre solution en utilisant decode('string-escape')fonctionne, mais vous devez être conscient de la quantité de mémoire que vous utilisez: trois fois la quantité d'utilisation codecs.open().

N'oubliez pas qu'un fichier n'est qu'une séquence d'octets de 8 bits. Ni les bits ni les octets n'ont de sens. C'est vous qui dites "65 signifie 'A'". Puisque \xc3\xa1devrait devenir "à" mais que l'ordinateur n'a aucun moyen de le savoir, vous devez le lui dire en précisant l'encodage qui a été utilisé lors de l'écriture du fichier.

Aaron Digulla
la source
Je pense qu'il manque des morceaux ici: le fichier f2 contient: hex: 0000000: 4361 7069 745c 7863 335c 7861 316e 0a Capit \ xc3 \ xa1n. codecs.open ('f2', 'rb', 'utf-8'), par exemple, les lit tous dans des caractères séparés (attendu) Existe-t-il un moyen d'écrire dans un fichier en ascii qui fonctionnerait?
Gregg Lind
6

sauf pour codecs.open(), on peut utiliser io.open()pour travailler avec Python2 ou Python3 pour lire / écrire un fichier unicode

exemple

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
la source
Oui, il est préférable d'utiliser io; Mais je l' ai écrit avec la déclaration comme ceci with io.open('data.txt', 'w', 'utf-8') as file:et a obtenu une erreur: TypeError: an integer is required. Après avoir changé pour with io.open('data.txt', 'w', encoding='utf-8') as file:et cela a fonctionné.
Evan Hu
5

Eh bien, votre éditeur de texte préféré ne se rend pas compte qu'il \xc3\xa1s'agit de littéraux de caractères, mais il les interprète comme du texte. C'est pourquoi vous obtenez la double barre oblique inverse dans la dernière ligne - c'est maintenant une vraie barre oblique inverse xc3, etc. dans votre fichier.

Si vous souhaitez lire et écrire des fichiers encodés en Python, utilisez au mieux le module codecs .

Coller du texte entre le terminal et les applications est difficile, car vous ne savez pas quel programme interprétera votre texte en utilisant quel encodage. Vous pouvez essayer ce qui suit:

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

Collez ensuite cette chaîne dans votre éditeur et assurez-vous qu'elle la stocke en utilisant Latin-1. Dans l'hypothèse où le presse-papiers ne brouille pas la chaîne, l'aller-retour devrait fonctionner.

Torsten Marek
la source
4

La séquence \ x .. est quelque chose de spécifique à Python. Ce n'est pas une séquence d'échappement d'octets universelle.

La façon dont vous entrez réellement dans le format non ASCII encodé en UTF-8 dépend de votre système d'exploitation et / ou de votre éditeur. Voici comment vous le faites dans Windows . Pour que OS X entre un avec un accent aigu, vous pouvez simplement appuyer sur option+ E, puis A, et presque tous les éditeurs de texte dans OS X prennent en charge UTF-8.

ʞɔıu
la source
3

Vous pouvez également améliorer la open()fonction d' origine pour travailler avec des fichiers Unicode en la remplaçant en place, à l'aide de la partialfonction. La beauté de cette solution est que vous n'avez pas besoin de modifier un ancien code. C'est transparent.

import codecs
import functools
open = functools.partial(codecs.open, encoding='utf-8')
hipertracker
la source
1

J'essayais d'analyser iCal en utilisant Python 2.7.9:

à partir du calendrier d'importation icalendar

Mais je recevais:

 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)

et il a été corrigé avec juste:

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

(Maintenant, il peut imprimer liké á böss.)

Alexx Roche
la source
0

J'ai trouvé l'approche la plus simple en changeant l'encodage par défaut de l'ensemble du script pour être «UTF-8»:

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

tout open, printou toute autre déclaration sera simplement utiliser utf8.

Fonctionne au moins pour Python 2.7.9.

Thx va à https://markhneedham.com/blog/2015/05/21/python-unicodeencodeerror-ascii-codec-cant-encode-character-uxfc-in-position-11-ordinal-not-in-range128/ ( regardez à la fin).

dr0i
la source