web-dev-qa-db-fra.com

Unicode (UTF-8) lit et écrit dans des fichiers en Python

Je ne parviens pas à comprendre le fait de lire et d’écrire du 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'

Donc, je tape Capit\xc3\xa1n dans 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? Il est clair qu'il me manque un peu de magie (ou de bon sens) essentielle. Que tape-t-on dans les fichiers texte pour obtenir les conversions appropriées?

Ce que je ne parviens vraiment pas à comprendre ici, c’est l’intérêt de la représentation UTF-8, si vous ne pouvez réellement obtenir que Python le reconnaisse, alors que cela vient de l’extérieur. Je devrais peut-être simplement JSON vider la chaîne de caractères et l’utiliser à la place, car elle a une représentation assiiable! Plus précisément, y a-t-il une représentation ASCII de cet objet Unicode que Python reconnaîtra et décodera, à partir 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'
298
Gregg Lind

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

vous avez "\ xc3" dans votre fichier. Ce sont 4 octets et dans votre code, vous les lisez tous. Vous pouvez le voir quand 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. Donc, vous avez quatre octets dans votre chaîne: "\", "x", "c" et "3".

Modifier:

Comme d'autres l'ont souligné dans leurs réponses, vous devez simplement entrer les caractères dans l'éditeur et votre éditeur doit alors 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 codec string_escape pour la décoder en chaîne normale:

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

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

Pour votre édition: vous n'avez pas UTF-8 dans votre fichier. Pour voir à quoi ça 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.out au contenu du fichier que vous avez enregistré avec votre éditeur.

104
unbeknown

Plutôt que de jouer avec les méthodes d'encodage et de décodage, il est plus facile de spécifier l'encodage lors de l'ouverture du fichier. Le module io) (ajouté dans Python 2.6) fournit une fonction io.open, dotée d'un paramètre de codage.

Utilisez la méthode open du module io.

>>>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 fonction io.open est un alias de la fonction intégrée open. La fonction open intégrée ne prend en charge que l'argument de codage dans Python 3, pas Python 2.

Edit: Auparavant, cette réponse recommandait le module codecs . Le module les codecs peut poser des problèmes lors du mélange de read() et readline() , cette réponse recommande donc à présent le module io .

Utilisez la méthode open 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 sera beaucoup moins déroutante.

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

666
Tim Swast

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

[Modifier le 2016-02-10 pour demander des éclaircissements]

Python3 a ajouté le paramètre d'encodage à sa fonction d'ouverture. Les informations suivantes sur la fonction open sont rassemblé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)

Encodage est le nom de l'encodage utilisé pour décoder ou encoder le fichier. Cela ne devrait être utilisé qu'en mode texte. Le codage par défaut dépend de la plate-forme (quel que soit locale.getpreferredencoding () renvoie), mais n'importe quel codage de texte pris en charge par Python peut être utilisé. Voir le module codecs pour la liste des encodages supportés.

Donc, en ajoutant encoding='utf-8' en tant que paramètre de la fonction open, la lecture et l’écriture du fichier se font sous la forme utf8 (qui est également le codage par défaut de tout ce qui est fait en Python.)

32
Dakusan

J'ai donc trouvé une solution à ce que je cherche:

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

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

Cela permet le type d’aller-retour que j’imaginais.

17
Gregg Lind
# -*- 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()
14
Ricardo

En fait, cela a fonctionné pour moi pour lire un fichier avec le codage UTF-8 dans Python 3.2:

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

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

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

Utile pour python serveurs http alimentés.

6
praj

à l'exception de 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
6
Ryan

Vous avez trébuché sur le problème général des encodages: comment savoir dans quel encodage se trouve un fichier?

Réponse: Vous ne pouvez que si le format de fichier le permet. XML, par exemple, commence par:

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

Cet en-tête a été choisi avec soin de manière à pouvoir être lu quel que soit le codage. Dans votre cas, aucun indice de ce type n'existe. Par conséquent, ni votre éditeur ni Python n'a la moindre idée de ce qui se passe. Par conséquent, vous devez utiliser le module codecs et utiliser codecs.open(path,mode,encoding), qui fournit le bit manquant en Python.

En ce qui concerne votre éditeur, vous devez vérifier s’il offre un moyen de définir le codage d’un fichier.

UTF-8 a pour objectif de pouvoir coder des caractères 21 bits (Unicode) sous forme de 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 des outils appropriés pour attacher les informations de codage aux fichiers du disque dur.

Le prochain numéro 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 l’ASCII. Pour afficher Unicode ou tout autre élément> = 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 entrer le tréma et enregistrer le fichier).

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

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

Comme vous pouvez le constater, la chaîne "\ xc3" a été transformée en un seul caractère. C'est maintenant une chaîne de 8 bits, codée en UTF-8. Pour obtenir Unicode:

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

Gregg Lind demandé: Je pense qu'il manque quelques 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 un caractère séparé (attendu). Existe-t-il un moyen d'écrire dans un fichier dans ASCII qui fonctionnerait?

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

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

Votre solution utilisant decode('string-escape') fonctionne, mais vous devez savoir combien de mémoire vous utilisez: Trois fois plus que d'utiliser codecs.open().

Rappelez-vous qu'un fichier est juste une séquence d'octets de 8 bits. Ni les bits ni les octets n'ont de signification. C'est toi qui dit "65 signifie 'A'". Puisque \xc3\xa1 devrait devenir "à" mais que l'ordinateur n'a aucun moyen de le savoir, vous devez le préciser en spécifiant le codage utilisé lors de l'écriture du fichier.

6
Aaron Digulla

Eh bien, votre éditeur de texte préféré ne se rend pas compte que \xc3\xa1 sont supposés être des littéraux, mais il les interprète comme du texte. C'est pourquoi vous avez les doubles barres obliques inverses dans la dernière ligne - c'est maintenant une vraie barre oblique inverse + xc3, etc. dans votre fichier.

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

Il est difficile de coller du texte entre le terminal et les applications, car vous ne savez pas quel programme interprétera votre texte avec quel codage. Vous pouvez essayer ce qui suit:

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

Ensuite, collez cette chaîne dans votre éditeur et assurez-vous qu'il la stocke avec Latin-1. Dans l’hypothèse où le presse-papiers ne brouille pas la ficelle, l’aller-retour devrait fonctionner.

5
Torsten Marek

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

La manière dont vous entrez réellement en non-ASCII codé 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 Aet presque tous les éditeurs de texte sous OS X prennent en charge UTF-8.

4
ʞɔıu

Vous pouvez également améliorer la fonction open() d'origine pour qu'elle fonctionne avec les fichiers Unicode en la remplaçant à la place, à l'aide de la fonction partial. L'avantage de cette solution est qu'il n'est pas nécessaire de modifier un ancien code. C'est transparent.

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

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

import icalendar Calendrier

Mais je devenais:

 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 cela a été corrigé avec juste:

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

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

1
Alexx Roche