Je lance cet extrait deux fois, dans le terminal Ubuntu (encodage défini sur utf-8), une fois avec ./test.py
puis 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 un UnicodeDecodeError. Quelqu'un peut-il expliquer pourquoi j'obtiens l'erreur uniquement dans le deuxième cas, ou mieux encore donner une explication détaillée de ce qui se passe derrière le rideau dans les deux cas?
Réponses:
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 / tableau d' octets. Cette distinction a été longtemps ignorée du fait de l'ubiquité historique des encodages ne dépassant pas 256 caractères (ASCII, Latin-1, Windows-1252, Mac OS Roman,…): ces encodages mappent un ensemble de caractères communs à nombres entre 0 et 255 (c'est-à-dire octets); l'échange relativement limité de fichiers avant l'avènement du web rendait tolérable cette situation d'encodages incompatibles, 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: de tels programmes traiter le texte comme des octets (via le codage utilisé par le système d'exploitation). La vue correcte et moderne sépare correctement ces deux concepts de chaîne, en fonction des deux points suivants:
Les personnages sont pour la plupart sans rapport avec les ordinateurs : on peut les dessiner sur un tableau noir, etc., comme par exemple بايثون, 中 蟒 et 🐍. Les «caractères» pour les machines incluent également les «instructions de dessin» comme par exemple les espaces, le retour chariot, les instructions pour définir le sens d'écriture (pour l'arabe, etc.), les accents, etc. Une très grande liste de caractères est incluse dans le standard Unicode ; il couvre la plupart des personnages connus.
D'un autre côté, les ordinateurs doivent représenter des caractères abstraits d'une manière ou d'une autre: pour cela, ils utilisent des tableaux d'octets (nombres compris entre 0 et 255), car leur mémoire est constituée de blocs d'octets. Le processus nécessaire qui convertit les caractères en octets est appelé encodage . Ainsi, un ordinateur nécessite un encodage pour représenter des caractères. Tout texte présent sur votre ordinateur est encodé (jusqu'à ce qu'il soit affiché), qu'il soit envoyé à un terminal (qui attend des caractères encodés d'une manière spécifique), ou enregistré dans un fichier. Afin d'être affichés ou correctement «compris» (par exemple, par l'interpréteur Python), les flux d'octets sont décodés 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 encodages pour ces caractères - il y a encore des endroits où l'on voit l'expression «encodage Unicode» comme un façon de faire référence à l'omniprésent UTF-8, mais c'est une terminologie incorrecte, car Unicode fournit plusieurs encodages).
En résumé, les ordinateurs doivent représenter en interne des caractères avec des octets , et ils le font via deux opérations:
Certains encodages ne peuvent pas encoder tous les caractères (par exemple, ASCII), tandis que (certains) encodages Unicode vous permettent d'encoder tous les caractères Unicode. Le codage n'est pas non plus nécessairement unique , car certains caractères peuvent être représentés soit directement, soit sous forme de combinaison (par exemple d'un caractère de base et d'accents).
Notez que le concept de nouvelle ligne ajoute une couche de complication , car il peut être représenté par différents (contrôle) caractères qui dépendent du système d'exploitation (ce qui est la raison de Python en mode lecture de fichier universel newline ).
Maintenant, ce que j'ai appelé « caractère » ci - dessus est ce que Unicode appelle un « caractère perçu par l' utilisateur ». Un seul caractère perçu par l'utilisateur peut parfois être représenté en Unicode en combinant des parties de caractère (caractère de base, accents,…) trouvés à différents index dans la liste Unicode, qui sont appelés " points de code " - ces points de codes peuvent être combinés pour former un "cluster de graphèmes". Unicode conduit ainsi à un troisième concept de chaîne, constitué d'une séquence de points de code Unicode, qui se situe entre des chaînes d'octets et de caractères, et qui est plus proche de ces dernières. Je les appellerai " chaînes Unicode " (comme dans Python 2).
Alors que Python peut imprimer des chaînes de caractères (perçus par l'utilisateur), les chaînes Python non octets sont essentiellement des séquences de points de code Unicode , et non de caractères perçus par l'utilisateur. Les valeurs de point de code sont celles utilisées dans la syntaxe de chaîne Python
\u
et\U
Unicode. Ils ne doivent pas être confondus avec le codage d'un caractère (et ne doivent pas avoir de relation avec lui: les points de code Unicode peuvent être codés de différentes manières).Cela a une conséquence importante: la longueur d'une chaîne Python (Unicode) est son nombre de points de code, qui n'est pas toujours son nombre de caractères perçus par l'utilisateur : ainsi
s = "\u1100\u1161\u11a8"; print(s, "len", len(s))
(Python 3) donne각 len 3
malgrés
avoir un seul utilisateur perçu (coréen) caractère (car il est représenté avec 3 points de code - même si ce n'est pas obligatoire, comme leprint("\uac01")
montre). Cependant, dans de nombreuses circonstances pratiques, la longueur d'une chaîne correspond au nombre de caractères perçus par l'utilisateur, car de nombreux caractères sont généralement stockés par Python en tant que point de code Unicode unique.En Python 2 , les chaînes Unicode sont appelées… "Chaînes Unicode" (
unicode
type, forme littéraleu"…"
), tandis que les tableaux d'octets sont des "chaînes" (str
type, où le tableau d'octets peut par exemple être construit avec des chaînes littérales"…"
). Dans Python 3 , les chaînes Unicode sont simplement appelées "chaînes" (str
type, forme littérale"…"
), tandis que les tableaux d'octets sont des "octets" (bytes
type, forme littéraleb"…"
). 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 le seul caractère).Avec ces quelques points clés, vous devriez être capable de comprendre la plupart des questions liées à l'encodage!
Normalement, lorsque vous imprimez
u"…"
sur un terminal , vous ne devriez pas avoir de déchets: Python connaît l'encodage de votre terminal. En fait, vous pouvez vérifier le codage attendu par le terminal: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 (au pire, la police du terminal n'a pas certains des caractères et imprimera une sorte de blanc à la place).
Si vos caractères d'entrée ne peuvent pas être codés avec le codage du terminal, cela signifie que le terminal n'est pas configuré pour afficher ces caractères. Python se plaindra (en Python avec un
UnicodeEncodeError
car la chaîne de caractères ne peut pas être encodée d'une manière qui convient à votre terminal). La seule solution possible est d'utiliser un terminal capable d'afficher les caractères (soit en configurant le terminal pour qu'il accepte un encodage pouvant représenter vos caractères, soit en utilisant un programme de terminal différent). Ceci est important lorsque vous distribuez des programmes qui peuvent être utilisés dans différents environnements: les messages que vous imprimez doivent être représentables dans le terminal de l'utilisateur. Parfois, il est donc préférable de s'en tenir aux chaînes qui ne contiennent que des caractères ASCII.Cependant, lorsque vous redirigez ou redirigez la sortie de votre programme, il n'est généralement pas possible de savoir quel est le codage d'entrée du programme récepteur, et le code ci-dessus renvoie un codage par défaut: Aucun (Python 2.7) ou UTF-8 ( Python 3):
Le codage de stdin, stdout et stderr peut cependant être défini via la
PYTHONIOENCODING
variable d'environnement, si nécessaire:Si l'impression sur un terminal ne produit pas ce que vous attendez, vous pouvez vérifier que le codage UTF-8 que vous avez mis 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:
Pour Python 3, vous pouvez vérifier l' une des questions posées précédemment sur StackOverflow.
la source
Python encode toujours les chaînes Unicode lors de l'écriture dans un terminal, un fichier, un tube, 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 tube, Python utilise par défaut l'encodage 'ascii', sauf indication contraire explicite. Python peut savoir quoi faire lors du transfert de la sortie via la
PYTHONIOENCODING
variable d'environnement. Un shell peut définir cette variable avant de rediriger la sortie Python vers un fichier ou un tube afin que le codage correct soit connu.Dans votre cas, vous avez imprimé 4 caractères inhabituels que votre terminal ne prend pas en charge dans sa police. Voici quelques exemples pour aider à expliquer le comportement, avec des caractères qui sont réellement pris en charge par mon terminal (qui utilise cp437, pas UTF-8).
Exemple 1
Notez que le
#coding
commentaire indique le codage dans lequel le fichier source est enregistré. J'ai choisi utf8 pour pouvoir prendre en charge les caractères dans la source que mon terminal ne pouvait pas. L'encodage est redirigé vers stderr afin qu'il puisse être vu lorsqu'il est redirigé vers un fichier.Sortie (exécuter directement à partir du terminal)
Python a correctement déterminé l'encodage du terminal.
Sortie (redirigé vers le fichier)
Python n'a pas pu déterminer le codage (Aucun) donc utilisé par défaut «ascii». ASCII ne prend en charge que la conversion des 128 premiers caractères d'Unicode.
Sortie (redirigé vers le fichier, PYTHONIOENCODING = cp437)
et mon fichier de sortie était correct:
Exemple 2
Maintenant, je vais ajouter un caractère dans la source qui n'est pas pris en charge par mon terminal:
Sortie (exécuter directement à partir du terminal)
Mon terminal n'a pas compris ce dernier caractère chinois.
Sortie (exécuter directement, PYTHONIOENCODING = 437: remplacer)
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
etxmlcharrefreplace
quelques 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 la police utilisée pour afficher les caractères doit toujours les prendre en charge.la source
PYTHONIOENCODING
. Faireprint string.encode("UTF-8")
comme suggéré par @Ismail a fonctionné pour moi.chcp
page de codes ne les prend pas en charge. Pour éviterUnicodeEncodeError: 'charmap'
, vous pouvez installer lewin-unicode-console
package.PYTHONIOENCODING=utf-8
résout le problème.Encodez-le lors de l'impression
En effet, lorsque vous exécutez le script manuellement, python l'encode avant de le sortir du terminal, lorsque vous le dirigez, python ne l'encode pas lui-même, vous devez donc encoder manuellement lors des E / S.
la source
win-unicode-console
(Windows), ou acceptez un paramètre de ligne de commande (si vous devez).