web-dev-qa-db-fra.com

Comment travailler avec des paires de substitution en Python?

Ceci est une suite de Conversion en Emoji . Dans cette question, l'OP disposait d'un fichier json.dumps()-encoded avec un emoji représenté par une paire de substitution - \ud83d\ude4f. Il/elle avait des problèmes pour lire le fichier et traduire l’emoji correctement, et le réponse correct était de json.loads() chaque ligne du fichier, et le module json traiterait la conversion de la paire de substitution en (je suppose Emoji codé en UTF8).

Voici donc ma situation: disons que je n'ai qu'une chaîne unicode Python 3 régulière avec une paire de substitution dans celle-ci:

emoji = "This is \ud83d\ude4f, an emoji."

Comment puis-je traiter cette chaîne pour obtenir une représentation du emoji out? Je cherche à obtenir quelque chose comme ça:

"This is ????, an emoji."
# or
"This is \U0001f64f, an emoji."

J'ai essayé:

print(emoji)
print(emoji.encode("utf-8")) # also tried "ascii", "utf-16", and "utf-16-le"
json.loads(emoji) # and `.encode()` with various codecs

Généralement, j'obtiens une erreur similaire à UnicodeEncodeError: XXX codec can't encode character '\ud83d' in position 8: surrogates no allowed.

J'exécute Python 3.5.1 sur Linux, avec $LANG défini sur en_US.UTF-8. J'ai exécuté ces exemples à la fois dans l'interpréteur Python sur la ligne de commande et dans IPython sous Sublime Text - il ne semble pas y avoir de différence.

19
MattDMo

Vous avez mélangé une chaîne littérale \ud83d dans un fichier json sur le disque (six caractères: \ u d 8 3 d) et un unique caractère u'\ud83d' (spécifié à l'aide d'un littéral de chaîne dans le code source Python) en mémoire. C'est la différence entre len(r'\ud83d') == 6 et len('\ud83d') == 1 sur Python 3.

Si vous voyez '\ud83d\ude4f' chaîne Python ( 2 caractères), il y a un bogue en amont. Normalement, vous ne devriez pas avoir une telle chaîne. Si vous en obtenez un et que vous ne pouvez pas réparer en amont, cela le génère; vous pouvez le réparer en utilisant le gestionnaire d'erreurs surrogatepass:

>>> "\ud83d\ude4f".encode('utf-16', 'surrogatepass').decode('utf-16')
'????'

Python 2 était plus permissif .

Remarque: même si votre fichier json contient des caractères littéraux\ud83d\ude4f ( 12 caractères); vous ne devriez pas obtenir la paire de substitution:

>>> print(ascii(json.loads(r'"\ud83d\ude4f"')))
'\U0001f64f'

Remarque: le résultat est 1 caractère ('\U0001f64f'), pas la paire de substitution ('\ud83d\ude4f').

27
jfs

Comme il s’agit d’une question récurrente et que le message d’erreur est légèrement obscur, voici une explication plus détaillée.

Les substituts sont un moyen d’exprimer des points de code Unicode supérieurs à U + FFFF.

Rappelez-vous que Unicode avait été spécifié à l'origine pour contenir 65 536 caractères, mais qu'il a rapidement été constaté que cela ne suffisait pas pour prendre en charge tous les glyphes du monde.

En tant que mécanisme d'extension pour le codage (sinon à largeur fixe) UTF-16 , une zone réservée a été configurée pour contenir un mécanisme permettant d'exprimer des points de code en dehors du Plan multilingue de base : Tout point de code présent. une zone spéciale doit être suivie par un autre code de caractère de la même zone. Ensemble, ils expriment un point de code avec un nombre plus grand que l'ancienne limite.

(À proprement parler, la zone des substituts est divisée en deux moitiés; le premier substitut dans une paire doit provenir de la moitié des hauts substituts et le second des bas substituts.)

Il s'agit d'un mécanisme hérité destiné à prendre en charge le codage UTF-16 de manière spécifique, et ne devrait pas être utilisé dans d'autres codages.

En d'autres termes, alors que U + 12345 peut être exprimé avec la paire de substitution U + D808 U + DF45, vous devez simplement l'exprimer directement à la place.

Plus en détail, voici comment cela serait exprimé dans UTF-8 en tant que caractère unique:

0xF0 0x92 0x8D 0x85

Et voici la séquence de substitution correspondante:

0xED 0xA0 0x88
0xED 0xBD 0x85

Comme déjà suggéré dans la réponse acceptée, vous pouvez aller-retour avec quelque chose comme

>>> "\ud808\udf45".encode('utf-16', 'surrogatepass').decode('utf-16').encode('utf-8')
b'\xf0\x92\x8d\x85'

Peut-être aussi voir http://www.russellcottrell.com/greek/utilities/surrogatepaircalculator.htm

3
tripleee