103 votes

UnicodeDecodeError lors de la redirection vers un fichier

J'ai exécuté cet extrait deux fois, dans le terminal Ubuntu (encodage réglé sur utf-8), une fois avec ./test.py et ensuite avec ./test.py >out.txt :

uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni

Sans redirection, il imprime des déchets. Avec la redirection, j'obtiens une UnicodeDecodeError. Quelqu'un peut-il m'expliquer pourquoi je n'obtiens l'erreur que dans le second cas, ou mieux encore, me donner une explication détaillée de ce qui se passe derrière le rideau dans les deux cas ?

0 votes

Ce site pourrait également vous aider.

0 votes

Lorsque j'essaie de reproduire votre constat, j'obtiens une UnicodeEncodeError, et non une UnicodeDecodeError. gist.github.com/jaraco/12abfc05872c65a4f3f6cd58b6f9be4d

257voto

EOL Points 24342

La clé de ces problèmes d'encodage est de comprendre qu'il existe en principe Deux concepts distincts de "chaîne". : (1) chaîne de caractères et (2) chaîne/réseau de octets . Cette distinction a longtemps été ignorée en raison de l'omniprésence historique des codages ne comportant pas plus de 256 caractères (ASCII, Latin-1, Windows-1252, Mac OS Roman, ) : ces codages font correspondre un ensemble de caractères communs à des nombres compris entre 0 et 255 (c'est-à-dire des octets) ; l'échange relativement limité de fichiers avant l'avènement du web rendait cette situation d'encodages incompatibles tolérable, car la plupart des programmes pouvaient ignorer le fait qu'il y avait plusieurs encodages tant qu'ils produisaient du texte qui restait sur le même système d'exploitation : ces programmes traitaient simplement le texte comme des octets (à travers l'encodage utilisé par le système d'exploitation). La vision correcte et moderne sépare correctement ces deux concepts de chaîne de caractères, en se basant sur les deux points suivants :

  1. Personnages sont pour la plupart sans rapport avec les ordinateurs Les "caractères" pour les machines comprennent également les "instructions de dessin", comme par exemple les espaces, le retour chariot, les instructions pour définir le sens de l'écriture (pour l'arabe, etc.), les accents, etc. A très grande liste de caractères est inclus dans le Unicode standard ; il couvre la plupart des caractères connus.

  2. D'autre part, les ordinateurs ont besoin de représenter les caractères abstraits d'une manière ou d'une autre : pour cela, ils utilisent tableaux d'octets (chiffres compris entre 0 et 255 inclus), car leur mémoire se présente sous forme de blocs d'octets. Le processus nécessaire pour convertir les caractères en octets est appelé codage . Ainsi, un ordinateur nécessite un codage afin de représenter les caractères. Tout texte présent sur votre ordinateur est codé (jusqu'à ce qu'il soit affiché), qu'il soit envoyé à un terminal (qui attend des caractères codés d'une manière spécifique), ou enregistré dans un fichier. Afin d'être affichés ou correctement "compris" (par l'interpréteur Python, par exemple), les flux d'octets sont décodé en caractères. Quelques encodages (UTF-8, UTF-16, ) sont définis par Unicode pour sa liste de caractères (Unicode définit donc à la fois une liste de caractères et des codages pour ces caractères - il y a encore des endroits où l'on voit l'expression "codage Unicode" comme un moyen de se référer à l'omniprésent UTF-8, mais c'est une terminologie incorrecte, car Unicode fournit multiple codages).

En résumé, les ordinateurs doivent représenter en interne les caractères avec des octets et ils le font par le biais de deux opérations :

Encodage : caractères octets

Décodage : octets caractères

Certains codages ne peuvent pas coder tous les caractères (par exemple, ASCII), tandis que (certains) codages Unicode vous permettent de coder tous les caractères Unicode. L'encodage n'est pas non plus nécessairement unique car certains caractères peuvent être représentés soit directement, soit sous la forme d'une combinaison de caractères. combinaison (par exemple, d'un caractère de base et d'accents).

Notez que le concept de nouvelle ligne ajoute une couche de complication puisqu'il peut être représenté par différents caractères (de contrôle) qui dépendent du système d'exploitation (c'est la raison pour laquelle Python est doté de l'option mode de lecture universel des fichiers de nouvelles lignes ).


Quelques informations supplémentaires sur Unicode, les caractères et les points de code, si vous êtes intéressé :

Maintenant, ce que j'ai appelé "caractère" ci-dessus est ce qu'Unicode appelle un " caractère perçu par l'utilisateur ". Un seul caractère perçu par l'utilisateur peut parfois être représenté dans Unicode en combinant des parties de caractères (caractère de base, accents, ) se trouvant à différents endroits de la chaîne de production. indices dans la liste Unicode, qui sont appelés " points de code " - ces points de code peuvent être combinés ensemble pour former un " cluster de graphèmes ". Unicode conduit donc à un troisième concept de chaîne, constitué d'une séquence de points de code Unicode, qui se situe entre les chaînes d'octets et les chaînes de caractères, et qui se rapproche de ces dernières. Je les appellerai " Chaînes de caractères Unicode " (comme dans Python 2).

Alors que Python peut imprimer des chaînes de caractères (perçues par l'utilisateur), Les chaînes de caractères non octets de Python sont essentiellement des séquences de points de code Unicode. et non des caractères perçus par l'utilisateur. Les valeurs des points de code sont celles utilisées dans le langage Python. \u et \U Syntaxe des chaînes de caractères Unicode. Ils ne doivent pas être confondus avec l'encodage d'un caractère (et ne doivent pas avoir de relation avec celui-ci : Les points de code Unicode peuvent être encodés de diverses manières).

Cela a une conséquence importante : la longueur d'une chaîne de caractères Python (Unicode) est son nombre de points de code, soit pas toujours son nombre de caractères perçus par l'utilisateur : donc s = "\u1100\u1161\u11a8"; print(s, "len", len(s)) (Python 3) donne len 3 malgré s avoir un seul caractère perçu par l'utilisateur (coréen) (parce qu'il est représenté par 3 points de code - même s'il n'est pas nécessaire, comme le montre l'exemple ci-dessous). print("\uac01") spectacles). Toutefois, dans de nombreuses circonstances pratiques, la longueur d'une chaîne de caractères correspond au nombre de caractères perçus par l'utilisateur, car de nombreux caractères sont généralement stockés par Python sous la forme d'un seul point de code Unicode.

Sur Python 2 les chaînes Unicode sont appelées "chaînes Unicode" ( unicode type, forme littérale u"…" ), tandis que les tableaux d'octets sont des "chaînes" ( str où le tableau d'octets peut par exemple être construit avec des chaînes de caractères. "…" ). Sur le site Python 3 Les chaînes de caractères Unicode sont simplement appelées "chaînes de caractères" ( str type, forme littérale "…" ), alors que les tableaux d'octets sont des "octets" ( bytes type, forme littérale b"…" ). En conséquence, quelque chose comme ""[0] donne un résultat différent dans Python 2 ( '\xf0' un octet) et Python 3 ( "" le premier et unique caractère).

Avec ces quelques points clés, vous devriez être en mesure de comprendre la plupart des questions relatives à l'encodage !


Normalement, lorsque vous imprimer u"…" à un terminal vous ne devriez pas obtenir de déchets : Python connaît l'encodage de votre terminal. En fait, vous pouvez vérifier quel est l'encodage attendu par le terminal :

% python
Python 2.7.6 (default, Nov 15 2013, 15:20:37) 
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.2.79)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print sys.stdout.encoding
UTF-8

Si vos caractères d'entrée peuvent être encodés avec l'encodage du terminal, Python le fera et enverra les octets correspondants à votre terminal sans se plaindre. Le terminal fera alors de son mieux pour afficher les caractères après avoir décodé les octets d'entrée (dans le pire des cas, la police du terminal ne possède pas certains des caractères et imprimera une sorte de blanc à la place).

Si vos caractères d'entrée ne peuvent pas être encodés avec l'encodage du terminal, cela signifie que le terminal n'est pas configuré pour afficher ces caractères. Python se plaindra (en Python avec une icône UnicodeEncodeError puisque la chaîne de caractères ne peut pas être codée d'une manière adaptée à votre terminal). La seule solution possible est d'utiliser un terminal qui peut afficher les caractères (soit en configurant le terminal de façon à ce qu'il accepte un codage qui puisse représenter vos caractères, soit en utilisant un autre programme de terminal). Ceci est important lorsque vous distribuez des programmes qui peuvent être utilisés dans différents environnements : les messages que vous imprimez doivent pouvoir être représentés dans le terminal de l'utilisateur. Il est donc parfois préférable de s'en tenir à des chaînes de caractères qui ne contiennent que des caractères ASCII.

Cependant, lorsque vous redirige ou pipe la sortie de votre programme, il n'est généralement pas possible de savoir quel est l'encodage d'entrée du programme récepteur, et le code ci-dessus renvoie un encodage par défaut : Aucun (Python 2.7) ou UTF-8 (Python 3) :

% python2.7 -c "import sys; print sys.stdout.encoding" | cat
None
% python3.4 -c "import sys; print(sys.stdout.encoding)" | cat
UTF-8

L'encodage de stdin, stdout et stderr peut cependant être set à travers le PYTHONIOENCODING la variable d'environnement, si nécessaire :

% PYTHONIOENCODING=UTF-8 python2.7 -c "import sys; print sys.stdout.encoding" | cat
UTF-8

Si l'impression vers un terminal ne produit pas ce que vous attendez, vous pouvez vérifier que le codage UTF-8 que vous avez introduit manuellement est correct ; par exemple, votre premier caractère ( \u001A ) n'est pas imprimable, si je ne me trompe pas .

Sur http://wiki.python.org/moin/PrintFails vous pouvez trouver une solution comme la suivante, pour Python 2.x :

import codecs
import locale
import sys

# Wrap sys.stdout into a StreamWriter to allow writing unicode.
sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout) 

uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni

Pour Python 3, vous pouvez vérifier une des questions posées précédemment sur StackOverflow.

2 votes

@singularity : Merci ! J'ai ajouté quelques informations pour Python 3.

2 votes

Merci, mec ! J'avais besoin de cette explication depuis si longtemps... C'est dommage que je ne puisse vous donner qu'un seul vote positif.

3 votes

Je suis heureux d'avoir pu vous aider, @m01 ! Une des motivations pour écrire cette réponse était qu'il y avait beaucoup de pages sur le web à propos d'Unicode et de Python, mais je trouvais que malgré leur intérêt, elles ne me permettaient jamais complètement de résoudre des problèmes concrets d'encodage Je crois vraiment qu'en gardant à l'esprit les principes trouvés dans cette réponse et prendre le temps de les utiliser pour résoudre des problèmes concrets de codage aide beaucoup.

21voto

Mark Tolonen Points 32702

Python encode toujours les chaînes Unicode lorsqu'il écrit dans un terminal, un fichier, un pipe, etc. Lors de l'écriture dans un terminal, Python peut généralement déterminer l'encodage du terminal et l'utiliser correctement. Lors de l'écriture dans un fichier ou un pipe, Python utilise par défaut l'encodage "ascii", sauf indication contraire explicite. Il est possible d'indiquer à Python ce qu'il doit faire lors de l'acheminement de la sortie par le biais de la commande PYTHONIOENCODING variable d'environnement. Un interpréteur de commandes peut définir cette variable avant de rediriger la sortie Python vers un fichier ou un tube afin de connaître le codage correct.

Dans votre cas, vous avez imprimé 4 caractères peu courants que votre terminal ne prenait pas en charge dans sa police. Voici quelques exemples pour vous aider à expliquer le comportement, avec des caractères qui sont effectivement pris en charge par mon terminal (qui utilise cp437, pas UTF-8).

Exemple 1

Notez que le #coding indique l'encodage dans lequel le fichier source est enregistré. J'ai choisi utf8 pour pouvoir supporter des caractères dans les sources que mon terminal ne pouvait pas supporter. L'encodage est redirigé vers stderr afin qu'il puisse être vu lorsqu'il est redirigé vers un fichier.

#coding: utf8
import sys
uni = u''
print >>sys.stderr,sys.stdout.encoding
print uni

Sortie (directement à partir du terminal)

cp437

Python a correctement déterminé l'encodage du terminal.

Sortie (redirigée vers le fichier)

None
Traceback (most recent call last):
  File "C:\ex.py", line 5, in <module>
    print uni
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-13: ordinal not in range(128)

Python n'a pas pu déterminer l'encodage (None) et a donc utilisé 'ascii' par défaut. ASCII ne permet de convertir que les 128 premiers caractères d'Unicode.

Sortie (redirigée vers le fichier, PYTHONIOENCODING=cp437)

cp437

et mon fichier de sortie était correct :

C:\>type out.txt

Exemple 2

Maintenant, je vais ajouter un caractère dans la source qui n'est pas supporté par mon terminal :

#coding: utf8
import sys
uni = u'' # added Chinese character at end.
print >>sys.stderr,sys.stdout.encoding
print uni

Sortie (directement à partir du terminal)

cp437
Traceback (most recent call last):
  File "C:\ex.py", line 5, in <module>
    print uni
  File "C:\Python26\lib\encodings\cp437.py", line 12, in encode
    return codecs.charmap_encode(input,errors,encoding_map)
UnicodeEncodeError: 'charmap' codec can't encode character u'\u9a6c' in position 14: character maps to <undefined>

Mon terminal n'a pas compris le dernier caractère chinois.

Résultat (exécuté directement, PYTHONIOENCODING=437:replace)

cp437
?

Les gestionnaires d'erreurs peuvent être spécifiés avec l'encodage. Dans ce cas, les caractères inconnus ont été remplacés par ? . ignore et xmlcharrefreplace sont d'autres options. Lorsque vous utilisez UTF8 (qui prend en charge l'encodage de tous les caractères Unicode), les remplacements ne seront jamais effectués, mais l'option police utilisés pour afficher les caractères doivent toujours les supporter.

0 votes

Il n'est pas tout à fait vrai que "Lors de l'écriture dans un fichier ou un tube, Python utilise par défaut l'encodage 'ascii', sauf indication contraire explicite". En fait, Python 3 utilise UTF-8, sur Mac OS X/Fink.

2 votes

Oui, Python 3 utilise 'utf8' par défaut, mais d'après l'exemple de l'OP, il utilise Python 2.X, qui utilise 'ascii' par défaut.

0 votes

Je n'ai pas pu obtenir une sortie correcte en manipulant PYTHONIOENCODING . Faire print string.encode("UTF-8") comme suggéré par @Ismail a fonctionné pour moi.

12voto

ismail Points 19146

Encodez-le lors de l'impression

uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni.encode("utf-8")

C'est parce que lorsque vous exécutez le script manuellement, python l'encode avant de le sortir dans le terminal, lorsque vous le pipetez, python ne l'encode pas lui-même et vous devez donc l'encoder manuellement lors des E/S.

4 votes

Cela ne répond toujours pas à la question "qu'est-ce qui se passe ici ?". Pourquoi, de but en blanc, il décide de ne coder que lorsqu'il est redirigé, alors que cela est censé être complètement transparent pour le processus.

0 votes

Pourquoi python ne l'encode-t-il pas lors de la redirection ? Est-ce que python vérifie explicitement et décide qu'il fera les choses différemment juste pour être difficile ?

0 votes

Shell intercepte le pipe, Python devrait vérifier si stdout est un pipe.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X