Boris FELD - PyConFR, Toulouse - 2017
Quelle est la longueur de la chaîne Unicode suivante en Python 2?
len(u'😎')
Ça dépends de votre version de Python, c'est soit 1:
DOCKER_IMAGE=quay.io/pypa/manylinux1_x86_64
$> docker run -t -i $DOCKER_IMAGE /opt/python/cp27-cp27mu/bin/python \
-c "print len(u'\U0001f60e')"
1
Soit 2:
DOCKER_IMAGE=quay.io/pypa/manylinux1_x86_64
$> docker run -t -i $DOCKER_IMAGE /opt/python/cp27-cp27m/bin/python \
-c "print len(u'\U0001f60e')"
2
Dans quelle situation pouvez-vous rencontrer cette erreur ?
UnicodeEncodeError: 'ascii' codec can't encode character
.encode('ascii')
.decode('ascii')
.decode('utf-8')
Dans toutes ces situations!
>>> x = u'é'
>>> x.encode('ascii')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' in position 0: ordinal not in range(128)
>>> x.decode('ascii')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' in position 0: ordinal not in range(128)
>>> x.decode('utf-8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' in position 0: ordinal not in range(128)
Quand devez-vous utiliser chr
ou unichr
?
chr
.unichr
.chr
pour de l'ASCII et unichr
pour de l'Unicode.unichr
.Dans les années 60, l'American Standards Association a voulu répondre à cette question:
Mais il y a un problème, les ordinateurs ne comprennents que le binaire. Comment transformer du texte en binaire ?
On savait déjà convertier des entiers en binaire.
0 = 0000000
1 = 0000001
2 = 0000010
3 = 0000011
.............
127 = 1111111
Nous n'avons qu'à assigner à chaque lettre un entier de 0 à 127 nommé "code point"
.
Prenons la chaîne suivante:
"pyconfr"
Une chaîne est une succession de caractères:
assert list("pyconfr") == ['p', 'y', 'c', 'o', 'n', 'f', 'r']
assert type("pyparis"[0]) == <type 'str'>
assert len("pyparis"[0]) == 1
Un caractère peut tout représenter. Ça peut être une lettre, un chiffre ou même un emoji.
Pour récupérer le code point ASCII d'un caractère, on peut utiliser ord
:
assert ord("p") == 112
Pour l'inverse, on peut utiliser chr
:
assert chr(112) == "p"
p | y | c | o | n | f | r | |
Code Point | 112 | 121 | 99 | 111 | 110 | 102 | 114 |
p | y | p | a | r | i | s | |
Code Point | 112 | 121 | 99 | 111 | 110 | 102 | 114 |
Binary | 1110000 | 1111001 | 1100011 | 1101111 | 1101110 | 1100110 | 1110010 |
code point 🢧 encode 🢧 binaire
code point 🢤 decode 🢤 binaire
encode
est fait pour transformer une chaîne en binaire:
string = 'abc'
bytes = bytes.encode('ascii')
assert hex(bytes) == '616263'
decode
est fait pour transformed du binaire en une chaîne:
bytes = unhex('616263')
string = bytes.decode('ascii')
assert string == 'abc'
Chaque de ces méthodes accepte un paramètre encoding
pour spécifier l'algorithme de conversion à utiliser.
ASCII a résolu le problème pour les USA mais pas pour le reste du monde.
ASCII utilise uniquement les 7 premiers bits d'un octet. 01100001
Sur la plupart des ordinateurs, un octet est composé de 8 bits, on peut donc représenter plus de caractères.
Et ainsi de nouveaux standards sont nés...
Certains étaient basés sur ASCII et utilisaient le 8ème bit pour supporter les lettres accentuées par exemple, comme Latin1 qui définit la lettre É sur le code point 201.
Et d'autres standards, n'étaient pas du tout compatible avec ASCII; comme EBCDIC, utilisé sur les mainframes IBM, où le code point 75 1001011
représente le signe de ponctuation "." tandis qu'en ASCII il représente la lettre "A".
Et bien sûr ces standards n'étaient pas compatible entre eux...
Texte initial | a | b | ã | é |
Latin1 Code Point | 97 | 98 | 227 | 233 |
Latin1 encoding | 01100001 | 01100010 | 11100011 | 11101001 |
ASCII decoding | a | b | ERROR | ERROR |
Mac OS Roman decoding | a | b | „ | È |
EBCDIC decoding | / | ERROR | T | Z |
Un Standard pour les gouverner tous,
Un Standard pour les trouver,
Un Standard pour les amener tous,
et au nom du bien les lier
Unicode est un standard informatique qui permet des échanges de textes dans différentes langues [...] qui vise au codage de texte écrit en donnant à tout caractère de n'importe quel système d'écriture un nom et un identifiant numérique, et ce de manière unifiée, quelle que soit la plate-forme informatique ou le logiciel utilisés.
Unicode naquit en 1987-1988 grâce à la coordination entre Joe Becker de Xerox, mais aussi de Lee Collins et Mark Davis de Apple.
Les code points Unicode sont heureusement pour nous compatibles avec ASCII.
Le standard Unicode est constitué d'un répertoire de 128 172 caractères, couvrant 135 script modernes et historiques, ainsi que plusieurs ensembles de symboles.
ASCII ne définit que 127 caractères, Unicode en définit 1000 fois plus !
Les caractères Unicode sont répartis en plusieurs blocs:
Vous vous rappellez la table ASCII ?
Prenons le caractère Unicode €
.
Tout d'abors, déclarez l'encoding de vos sources Python comme utf-8
:
# -*- coding: utf-8 -*-
Ensuite vous pouvez l'écrire de cette manières:
u'€'
Ou:
u'\u20AC'
Son code point est 8364:
ord(u'€') == 8364
Essayons de convertir son code point en binaire:
€ | |
Code Point | 8364 |
Naive conversion | 00100000 10101100 |
Ça ne rentre plus dans 1 octet.
Les problèmes quand vous commencez à jouer avec des octets multiples:
8364 en binaire prend 2 octets. Les code points Unicode vont bien au delà de 1 000 000, utilisant ainsi au moins 3 octets.
Comme ASCII était simple, convertir des code points ASCII en binaire était simplissime.
Mais le présence de code points Unicode utilisant plus d'un octet complexifie le processus. Il y a plusieurs manières de le faire, appelés encodings:
Si vous n'êtes pas sûr, utilisez UTF-8
, il est compatible avec tous les caractères, marche bien la plupart du temps et a résolu les problèmes d'octets multiple de manières élégante.
Si vous traitez plus de caractères asiatiques que de caractères latin, utilisez UTF-16
pour utiliser moins de place et de mémoire.
Si vous interagissez avec un autre programme, utilisez l'encoding de cet autre programme (qui n'a pas déjà traité du CSV ?)
A | € | |
Code Point | 65 | 8364 |
Naive conversion | 01000001 | 00100000 10101100 |
UTF-8 | 01000001 | 11100010 10000010 10101100 |
UTF-16 | 00000000 01000001 | 00100000 10101100 |
UTF-32 | 00000000 00000000 00000000 01000001 | 00000000 00000000 00100000 10101100 |
Faisons le point sur quelque chose:
encode
est fait pour transformer une chaîne unicode en binaire:
hex(u'é'.encode('utf-8')) == 'c3a9'
decode
est fait pour transformer du binaire en chaîne unicode:
unhex('c3a9').decode('utf-8') == u'é'
Compter la longueur d'une chaîne ASCII est simple, il suffit de compter le nombre d'octets !
Mais c'est bien plus difficile avec les chaînes Unicode.
Python 2 essaye de vous donner une réponse correcte, mais n'y arrive pas toujours.
Prenons un caractères comme exemple: 😎
. Son code point est 128526
.
Python 2 est disponible en plusieurs versions dont 2 sont liés à la façon dont il gère l'Unicode. Cela peut être une version narrow
ou une version wide
. Cela change la façon dont Python stocke les chaînes.
Pour les code points < 65535, ça marche pareil, Python stocke chaque caractères séparement.
Pour les code points > 65535, ça change. Pour la version wide
, la taille allouée pour les caractères est suffisante pour tous les code points Unicode. Mais avec la version narrow
, le taille allouée n'est pas suffisante pour les code points > 65535, du coup Python stocke les code points comme une paire de caractères.
La version narrow
utilise ainsi moins de mémoire mais celà explique pourquoi cette version retourne 2
pour len(u'😎')
, c'est parce que Python 2 stocke deux caractères.
Vous vous rappelez la signification de encode
et decode
?
Encode transforme une chaîne Unicode en binaire.
Decode transforme du binaire en une chaîne Unicode.
Python 2 as toujours eut un type string mais as introduit le type Unicode en Python 2.1.
Le type str
en Python 2 est mal nommé parce que c'est simplement une successions d'octets. Quand vous essayez de l'afficher, Python va essayer de le décoder pour vous. Du coup pour les chaînes de caractères ASCII, encode et decode vont retourner la même chose:
x = 'abc'
assert x.encode('ascii') == x
assert x.decode('ascii') == x
Python 2 est un langage fortement typé, ce qui signifie qu'il ne va pas convertir de types derrière votre dos:
'012' + 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot concatenate 'str' and 'int' objects
Mais il ne respecte pas cette propriété avec les strings. Vous vous souvenez que decode
sert à convertir du binaire en une string Unicode en Python ?
x = u'é'
x.decode('utf-8')
Comme decode
n'est pas appelé sur des octets, Python va d'abord essayer de convertir la chaîne en binaire et va en fait exécuter :
x = u'é'
x.encode('ascii').decode('utf-8')
C'est pourquoi vous pouvez voir une erreur UnicodeEncodeError
quand vous essayez de décoder une chaîne Unicode en Python 2.
Vous pouvez utiliser chr
pour récupérer le caractère correspondant à un code point :
assert chr(65) == 'A'
Mais ça ne fonctionne qu'avec les code point ASCII !
chr(8364)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: chr() arg not in range(256)
Pour les code points Unicode, utilisez unichr
:
assert unichr(8364) == u'€'
Python 3 stocke maintenant ses chaînes de la même manière et len
retourne toujours la bonne réponse:
x = '😎'
assert len(x) == 1
Le plus gros changement dans Python 3 as été son système de type:
Binaire | Chaînes de caractères | Chaîne Unicode | |
Python 2 | str |
unicode |
|
Python 3 | bytes |
str |
Maintenant que Python 3 as un type séparé pour le binaire et les chaînes, on ne peut plus se tromper entre encode
et decode
:
string = ''
string.decode('ascii')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute 'decode'
Decoder une chaîne Unicode n'a jamais fais de sens.
bytes = b''
bytes.encode('utf-8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'bytes' object has no attribute 'encode'
Comme les chaînes Unicode sont maintenant la norme, Python 3 as supprimé le préfixe u
pour les chaînes et l'a replacé par un préfixe b
pour le binaire, vous pouvez directement écrire:
x = '😎'
Python 3.3 as réintroduit le préfix pour les bases de code qui doivent être compatibles avec Python 2 et 3, vous pouvez aussi écrire:
x = u'😎'
Python 3 n'a plus qu'une fonction chr
qui replace et combine chr
et unichr
:
assert chr(65) == 'A'
assert chr(8364) == '€'
Grâce au nouveau types de Python 3, il est maintenant plus facile d'identifier quelle partie du code doit encoder des chaînes et décoder du binaire.
binaire | Monde extérieur |
decode | Librairie |
unicode | Business logic |
unicode | |
encode | Librairie |
binaire | Monde extérieur |
Software should only work with Unicode strings internally, decoding the input data as soon as possible and encoding the output only at the end.
Vous ne pouvez pas deviner l'encoding d'une suite d'octets:
Content-Type: text/html; charset=ISO-8859-4
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<?xml version="1.0" encoding="UTF-8" ?>
# -*- coding: iso8859-1 -*-
Si vous devez vraiment vraiment vraiment vraiment deviner l'encoding, vous pouvez utilisez chardet, mais rappelez vous que c'est sans garantie.
encode
et decode
acceptent un deuxième argument pour la gestion des erreurs. Par défaut la valeur est strict
qui signifie de crasher:
x = u'abcé'
x.encode('ascii', errors='strict')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' in position 3...
Vous pouvez aussi utiliser replace
pour remplacer les caractères invalides par ?
:
assert x.encode('ascii', errors='replace') == 'abc?'
Ou vous pouvez simplement les ignorer:
assert x.encode('ascii', errors='ignore') == 'abc'
Finalement, vous pouvez aussi les remplacer par leur code XML:
assert x.encode('ascii', errors='xmlcharrefreplace') == 'abcé'
for c in range(0x1F410, 0x1F4f0):
print (r"\U%08x"%c).decode("unicode-escape"),
🐐 🐑 🐒 🐓 🐔 🐕 🐖 🐗 🐘 🐙 🐚 🐛 🐜 🐝 🐞 🐟 🐠 🐡 🐢 🐣 🐤 🐥 🐦 🐧 🐨 🐩 🐪 🐫 🐬 🐭 🐮 🐯 🐰 🐱 🐲 🐳 🐴 🐵 🐶 🐷 🐸 🐹 🐺 🐻 🐼 🐽 🐾 🐿 👀 👁 👂 👃 👄 👅 👆 👇 👈 👉 👊 👋 👌 👍 👎 👏 👐 👑 👒 👓 👔 👕 👖 👗 👘 👙 👚 👛 👜 👝 👞 👟 👠 👡 👢 👣 👤 👥 👦 👧 👨 👩 👪 👫 👬 👭 👮 👯 👰 👱 👲 👳 👴 👵 👶 👷 👸 👹 👺 👻 👼 👽 👾 👿 💀 💁 💂 💃 💄 💅 💆 💇 💈 💉 💊 💋 💌 💍 💎 💏 💐 💑 💒 💓 💔 💕 💖 💗 💘 💙 💚 💛 💜 💝 💞 💟 💠 💡 💢 💣 💤 💥 💦 💧 💨 💩 💪 💫 💬 💭 💮 💯 💰 💱 💲 💳 💴 💵 💶 💷 💸 💹 💺 💻 💼 💽 💾 💿 📀 📁 📂 📃 📄 📅 📆 📇 📈 📉 📊 📋 📌 📍 📎 📏 📐 📑 📒 📓 📔 📕 📖 📗 📘 📙 📚 📛 📜 📝 📞 📟 📠 📡 📢 📣 📤 📥 📦 📧 📨 📩 📪 📫 📬 📭 📮 📯
Table of Contents | t |
---|---|
Exposé | ESC |
Source Files | s |
Slide Numbers | n |
Notes | 2 |
Help | h |