Écrire du texte Unicode dans un fichier texte?

225

J'extrais des données d'un document Google, je les traite et je les écris dans un fichier (que je finirai par coller dans une page Wordpress).

Il contient des symboles non ASCII. Comment puis-je les convertir en toute sécurité en symboles pouvant être utilisés dans une source HTML?

Actuellement, je convertis tout en Unicode en cours de route, je rassemble le tout dans une chaîne Python, puis je fais:

import codecs
f = codecs.open('out.txt', mode="w", encoding="iso-8859-1")
f.write(all_html.encode("iso-8859-1", "replace"))

Il y a une erreur d'encodage sur la dernière ligne:

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

Solution partielle:

Ce Python s'exécute sans erreur:

row = [unicode(x.strip()) if x is not None else u'' for x in row]
all_html = row[0] + "<br/>" + row[1]
f = open('out.txt', 'w')
f.write(all_html.encode("utf-8"))

Mais ensuite, si j'ouvre le fichier texte réel, je vois beaucoup de symboles comme:

Qur’an 

Peut-être que je dois écrire dans autre chose qu'un fichier texte?

Simon
la source
1
Le programme que vous utilisez pour l'ouvrir n'interprète pas correctement le texte UTF-8. Il devrait avoir une option pour ouvrir le fichier en UTF-8.
Thomas K

Réponses:

322

Traitez exclusivement les objets unicode autant que possible en décodant les choses en objets unicode lorsque vous les obtenez pour la première fois et en les encodant si nécessaire à la sortie.

Si votre chaîne est en fait un objet Unicode, vous devrez la convertir en un objet chaîne codé Unicode avant de l'écrire dans un fichier:

foo = u'Δ, Й, ק, ‎ م, ๗, あ, 叶, 葉, and 말.'
f = open('test', 'w')
f.write(foo.encode('utf8'))
f.close()

Lorsque vous relisez ce fichier, vous obtenez une chaîne codée en unicode que vous pouvez décoder en un objet unicode:

f = file('test', 'r')
print f.read().decode('utf8')
quasi-stoïque
la source
Merci. Cela fonctionne sans erreur, mais si j'ouvre le fichier texte, je vois un tas de symboles étranges :) J'ai besoin de copier et coller le texte dans une page Wordpress (ne demandez pas). Existe-t-il un moyen d'imprimer les symboles qui s'y trouvent? Je suppose que ce n'est pas un fichier txt, non, mais peut-être quelque chose d'autre?
simon
1
Qu'utilisez-vous pour ouvrir le fichier texte? Je suppose que vous êtes sous Windows et que vous l'ouvrez dans le Bloc-notes, qui n'est pas trop intelligent avec des encodages. Que se passe-t-il lorsque vous l'ouvrez dans Wordpad?
quasistoïque
@quasistoic d'où vient la méthode du fichier ?
Omar Cusma Fait
J'avais besoin d'activer le mode binaire, c'est-à-dire f = open ('test', 'wb'), comme décrit dans stackoverflow.com/a/5513856/6580199 - sinon j'obtiendrais "TypeError: l'argument write () doit être str, pas d'octets "
Benji
72

Dans Python 2.6+, vous pouvez utiliser laio.open() valeur par défaut ( intégréeopen() ) sur Python 3:

import io

with io.open(filename, 'w', encoding=character_encoding) as file:
    file.write(unicode_text)

Cela peut être plus pratique si vous devez écrire le texte de manière incrémentielle (vous n'avez pas besoin d'appeler unicode_text.encode(character_encoding)plusieurs fois). Contrairement au codecsmodule, le iomodule a un support universel approprié pour les nouvelles lignes.

jfs
la source
1
Mec, j'ai passé tellement de temps à trouver ça! Je vous remercie!
Georgy Gobozov
2
Cela fonctionne également pour Python 3 (évident, mais mérite d'être souligné).
Hippo
37

La gestion des chaînes Unicode est déjà standardisée dans Python 3.

  1. les caractères sont déjà stockés en Unicode (32 bits) en mémoire
  2. Il vous suffit d'ouvrir le fichier dans utf-8
    (la conversion d'Unicode 32 bits en utf-8 de longueur variable est automatiquement effectuée de la mémoire vers le fichier.)

    out1 = "(嘉南大圳 ㄐㄧㄚ ㄋㄢˊ ㄉㄚˋ ㄗㄨㄣˋ )"
    fobj = open("t1.txt", "w", encoding="utf-8")
    fobj.write(out1)
    fobj.close()
david m lee
la source
Mais cela ne fonctionne pas sur Python 2, non? (Je devrais dire, sur ce code Python 3, il a l'air si concis et raisonnable)
Liwen Zhao
cela ne devrait pas fonctionner sur Python 2. Nous restons sur Python 3. 3 est tellement mieux.
david m lee
18

Le fichier ouvert par codecs.openest un fichier qui prend des unicodedonnées, les encode iso-8859-1et les écrit dans le fichier. Cependant, ce que vous essayez d'écrire ne l'est pas unicode; vous le prenez unicodeet le codez en iso-8859-1 vous-même . C'est ce que fait la unicode.encodeméthode, et le résultat de l'encodage d'une chaîne unicode est un bytestring (un strtype.)

Vous devez soit utiliser normal open()et encoder vous-même l'unicode, soit (généralement une meilleure idée) utiliser codecs.open()et non encoder les données vous-même.

Thomas Wouters
la source
17

Préface: votre spectateur fonctionnera-t-il?

Assurez-vous que votre visionneuse / éditeur / terminal (quelle que soit la manière dont vous interagissez avec votre fichier encodé utf-8) peut lire le fichier. Il s'agit souvent d'un problème sous Windows , par exemple, le Bloc-notes.

Écrire du texte Unicode dans un fichier texte?

En Python 2, utilisez à openpartir du iomodule (c'est le même que celui intégré opendans Python 3):

import io

Meilleure pratique, en général, utilisée UTF-8pour écrire dans des fichiers (nous n'avons même pas à nous soucier de l'ordre des octets avec utf-8).

encoding = 'utf-8'

utf-8 est l'encodage le plus moderne et universellement utilisable - il fonctionne dans tous les navigateurs Web, la plupart des éditeurs de texte (voir vos paramètres si vous avez des problèmes) et la plupart des terminaux / shells.

Sous Windows, vous pouvez essayer utf-16lesi vous êtes limité à l'affichage de la sortie dans le Bloc-notes (ou une autre visionneuse limitée).

encoding = 'utf-16le' # sorry, Windows users... :(

Et ouvrez-le avec le gestionnaire de contexte et écrivez vos caractères unicode:

with io.open(filename, 'w', encoding=encoding) as f:
    f.write(unicode_object)

Exemple utilisant de nombreux caractères Unicode

Voici un exemple qui tente de mapper tous les caractères possibles jusqu'à trois bits de large (4 est le maximum, mais cela irait un peu loin) de la représentation numérique (en nombres entiers) à une sortie imprimable codée, ainsi que son nom, si possible (mettez ceci dans un fichier appelé uni.py):

from __future__ import print_function
import io
from unicodedata import name, category
from curses.ascii import controlnames
from collections import Counter

try: # use these if Python 2
    unicode_chr, range = unichr, xrange
except NameError: # Python 3
    unicode_chr = chr

exclude_categories = set(('Co', 'Cn'))
counts = Counter()
control_names = dict(enumerate(controlnames))
with io.open('unidata', 'w', encoding='utf-8') as f:
    for x in range((2**8)**3): 
        try:
            char = unicode_chr(x)
        except ValueError:
            continue # can't map to unicode, try next x
        cat = category(char)
        counts.update((cat,))
        if cat in exclude_categories:
            continue # get rid of noise & greatly shorten result file
        try:
            uname = name(char)
        except ValueError: # probably control character, don't use actual
            uname = control_names.get(x, '')
            f.write(u'{0:>6x} {1}    {2}\n'.format(x, cat, uname))
        else:
            f.write(u'{0:>6x} {1}  {2}  {3}\n'.format(x, cat, char, uname))
# may as well describe the types we logged.
for cat, count in counts.items():
    print('{0} chars of category, {1}'.format(count, cat))

Cela devrait s'exécuter dans l'ordre d'une minute environ, et vous pouvez afficher le fichier de données, et si votre visionneuse de fichiers peut afficher unicode, vous le verrez. Informations sur les catégories peuvent être trouvées ici . Sur la base des décomptes, nous pouvons probablement améliorer nos résultats en excluant les catégories Cn et Co, auxquelles aucun symbole ne leur est associé.

$ python uni.py

Il affichera le mappage hexadécimal, la catégorie , le symbole (à moins qu'il ne puisse pas obtenir le nom, donc probablement un caractère de contrôle), et le nom du symbole. par exemple

Je recommande lesssur Unix ou Cygwin (n'imprimez pas / ne cattez pas le fichier entier à votre sortie):

$ less unidata

par exemple, affichera similaire aux lignes suivantes que j'ai échantillonnées à partir de celui-ci en utilisant Python 2 (unicode 5.2):

     0 Cc NUL
    20 Zs     SPACE
    21 Po  !  EXCLAMATION MARK
    b6 So    PILCROW SIGN
    d0 Lu  Ð  LATIN CAPITAL LETTER ETH
   e59 Nd    THAI DIGIT NINE
  2887 So    BRAILLE PATTERN DOTS-1238
  bc13 Lo    HANGUL SYLLABLE MIH
  ffeb Sm    HALFWIDTH RIGHTWARDS ARROW

Mon Python 3.5 d'Anaconda a unicode 8.0, je suppose que la plupart des 3 le feraient.

Aaron Hall
la source
3

Comment imprimer des caractères Unicode dans un fichier:

Enregistrez-le dans le fichier: foo.py:

#!/usr/bin/python -tt
# -*- coding: utf-8 -*-
import codecs
import sys 
UTF8Writer = codecs.getwriter('utf8')
sys.stdout = UTF8Writer(sys.stdout)
print(u'e with obfuscation: é')

Exécutez-le et dirigez la sortie vers le fichier:

python foo.py > tmp.txt

Ouvrez tmp.txt et regardez à l'intérieur, vous voyez ceci:

el@apollo:~$ cat tmp.txt 
e with obfuscation: é

Ainsi, vous avez enregistré unicode e avec une marque d'obscurcissement dessus dans un fichier.

Eric Leschinski
la source
2
J'étais assez excité par cette réponse, mais cela donne une erreur sur ma machine. Lorsque je copie / colle votre code, j'obtiens une erreur: "TypeError: doit être str, pas d'octets"
Richard Rast
1

Cette erreur se produit lorsque vous essayez de coder une chaîne non unicode: il essaie de la décoder, en supposant qu'elle soit en ASCII ordinaire. Il y a deux possibilités:

  1. Vous le codez en chaîne de bytestring, mais comme vous avez utilisé codecs.open, la méthode d'écriture attend un objet unicode. Vous l'encodez donc et il essaie de le décoder à nouveau. Essayez: à la f.write(all_html)place.
  2. all_html n'est pas, en fait, un objet unicode. Lorsque vous le faites .encode(...), il essaie d'abord de le décoder.
Thomas K
la source
0

En cas d'écriture en python3

>>> a = u'bats\u00E0'
>>> print a
batsà
>>> f = open("/tmp/test", "w")
>>> f.write(a)
>>> f.close()
>>> data = open("/tmp/test").read()
>>> data
'batsà'

En cas d'écriture en python2:

>>> a = u'bats\u00E0'
>>> f = open("/tmp/test", "w")
>>> f.write(a)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe0' in position 4: ordinal not in range(128)

Pour éviter cette erreur, vous devez le coder en octets en utilisant les codecs "utf-8" comme ceci:

>>> f.write(a.encode("utf-8"))
>>> f.close()

et décoder les données lors de la lecture en utilisant les codecs "utf-8":

>>> data = open("/tmp/test").read()
>>> data.decode("utf-8")
u'bats\xe0'

Et aussi si vous essayez d'exécuter l'impression sur cette chaîne, elle se décodera automatiquement en utilisant les codecs "utf-8" comme celui-ci

>>> print a
batsà
ashish14
la source