35 votes

Octets dans une chaîne de caractères unicode Python

En Python 2, les chaînes Unicode peuvent contenir à la fois des caractères Unicode et des octets :

a = u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439 \xd0\xb5\xd0\xba'

Je comprends que ceci est absolument quelque chose qu'on ne devrait pas écrire dans son propre code, mais c'est une chaîne avec laquelle je dois travailler.

Les octets dans la chaîne ci-dessus sont en UTF-8 pour (Unicode \u0435\u043a).

Mon objectif est d'obtenir une chaîne Unicode contenant tout en Unicode, c'est-à-dire (\u0420\u0443\u0441\u0441\u043a\u0438\u0439 \u0435\u043a).

L'encoder en UTF-8 donne

>>> a.encode('utf-8')
'\xd0\xa0\xd1\x83\xd1\x81\xd1\x81\xd0\xba\xd0\xb8\xd0\xb9 \xc3\x90\xc2\xb5\xc3\x90\xc2\xba'

Qui, décodé depuis UTF-8, donne la chaîne initiale avec des octets dedans, ce qui n'est pas bon :

>>> a.encode('utf-8').decode('utf-8')
u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439 \xd0\xb5\xd0\xba'

J'ai trouvé un moyen bricolé pour résoudre le problème, cependant :

>>> repr(a)
"u'\\u0420\\u0443\\u0441\\u0441\\u043a\\u0438\\u0439 \\xd0\\xb5\\xd0\\xba'"
>>> eval(repr(a)[1:])
'\\u0420\\u0443\\u0441\\u0441\\u043a\\u0438\\u0439 \xd0\xb5\xd0\xba'
>>> s = eval(repr(a)[1:]).decode('utf8')
>>> s
u'\\u0420\\u0443\\u0441\\u0441\\u043a\\u0438\\u0439 \u0435\u043a'
# Presque là, les octets sont corrects maintenant mais les anciens caractères réels Unicode
# sont maintenant échappés avec des \u ; il faut donc les déséchapper.
>>> import re
>>> re.sub(u'\\\\u([a-f\\d]+)', lambda x : unichr(int(x.group(1), 16)), s)
u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439 \u0435\u043a' # Succès !

Cela fonctionne bien mais semble très bricolé en raison de son utilisation de eval, repr, et ensuite de la regex supplémentaire de la représentation de la chaîne Unicode. Y a-t-il un moyen plus propre ?

23voto

Karl Knechtel Points 24349

En Python 2, les chaînes Unicode peuvent contenir à la fois des caractères Unicode et des octets :

Non, elles ne le peuvent pas. Elles contiennent des caractères Unicode.

Dans la chaîne d'origine, \xd0 n'est pas un octet faisant partie d'un encodage UTF-8. Il s'agit du caractère Unicode avec le point de code 208. u'\xd0' == u'\u00d0'. Il se trouve simplement que la fonction repr pour les chaînes Unicode en Python 2 préfère représenter les caractères avec des échappements \x lorsque c'est possible (c'est-à-dire les points de code < 256).

Il n'y a aucun moyen de regarder la chaîne et de dire que l'octet \xd0 est censé faire partie d'un caractère encodé en UTF-8, ou s'il représente effectivement ce caractère Unicode par lui-même.

Cependant, si vous supposez que vous pouvez toujours interpréter ces valeurs comme étant encodées, vous pourriez essayer d'écrire quelque chose qui analyse chaque caractère à tour de rôle (utilisez ord pour convertir en un entier de point de code), décode les caractères < 256 en UTF-8, et passe les caractères >= 256 tels quels.

13voto

georg Points 52691

(En réponse aux commentaires ci-dessus) : ce code convertit tout ce qui ressemble à de l'utf8 et laisse les autres points de code tels quels :

a = u'\u0420\u0443\u0441 utf:\xd0\xb5\xd0\xba bytes:bl\xe4\xe4'

def convert(s):
    try:
        return s.group(0).encode('latin1').decode('utf8')
    except:
        return s.group(0)

import re
a = re.sub(r'[\x80-\xFF]+', convert, a)
print a.encode('utf8')   

Résultat :

Рус utf:ек bytes:blää

12voto

beerbajay Points 6120

Le problème est que votre chaîne n'est pas réellement encodée dans un codage spécifique. Votre chaîne d'exemple :

a = u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439 \xd0\xb5\xd0\xba'

Mélange la représentation interne des chaînes Unicode de Python avec du texte encodé en utf-8. Si nous ne considérons que les caractères "spéciaux" :

>>> orig = u'\u0435\u043a'
>>> bytes = u'\xd0\xb5\xd0\xba'
>>> print orig
ек
>>> print bytes
ек

Mais vous dites, que bytes est encodé en utf-8 :

>>> print bytes.encode('utf-8')
ек
>>> print bytes.encode('utf-8').decode('utf-8')
ек

Faux ! Mais que dire de :

>>> bytes = '\xd0\xb5\xd0\xba'
>>> print bytes
ек
>>> print bytes.decode('utf-8')
ек

Hurrah.

Alors. Que signifie cela pour moi? Cela signifie que vous résolvez (probablement) le mauvais problème. Ce que vous devriez nous demander / essayer de comprendre, c'est pourquoi vos chaînes sont dans cette forme au départ et comment les éviter / les corriger avant de les mélanger toutes.

5voto

kev Points 41855

Vous devez convertir les unichrs en chrs, puis les décoder.

u'\xd0' == u'\u00d0' est Vrai

$ python
>>> import re
>>> a = u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439 \xd0\xb5\xd0\xba'
>>> re.sub(r'[\000-\377]*', lambda m:''.join([chr(ord(i)) for i in m.group(0)]).decode('utf8'), a)
u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439 \u0435\u043a'
  • r'[\000-\377]*' va correspondre aux unichrs u'[\u0000-\u00ff]*'
  • u'\xd0\xb5\xd0\xba' == u'\u00d0\u00b5\u00d0\u00ba'
  • Vous utilisez des octets encodés en utf8 comme points de code Unicode (voici le PROBLÈME)
  • Je résous le problème en faisant semblant que ces unichars erronés sont les octets correspondants
  • Je recherche tous ces unichars erronés, les convertis en caractères, puis les décode.

Si j'ai tort, veuillez me le dire.

5voto

Mark Tolonen Points 32702

Vous avez déjà une réponse, mais voici une façon de déchiffrer des séquences Unicode similaires à UTF-8 qui est moins susceptible de décoder des séquences Unicode en latin-1 par erreur. La fonction re.sub :

  1. Correspond aux caractères Unicode < U+0100 qui ressemblent à des séquences UTF-8 valides (réf : RFC 3629).
  2. Encode la séquence Unicode en sa séquence d'octets latin-1 équivalente.
  3. Décode la séquence en utilisant UTF-8 pour revenir en Unicode.
  4. Remplace la séquence similaire à l'UTF-8 d'origine par le caractère Unicode correspondant.

Veuillez noter que cela pourrait encore correspondre à une séquence Unicode si juste les bons caractères apparaissent les uns à côté des autres, mais c'est beaucoup moins probable.

import re

# votre exemple
a = u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439 \xd0\xb5\xd0\xba'

# caractères Unicode imprimables < 256.
a += ''.join(chr(n) for n in range(32,256)).decode('latin1')

# quelques caractères UTF-8 décodés en latin1.
a += ''.join(unichr(n) for n in [2**7-1,2**7,2**11-1,2**11]).encode('utf8').decode('latin1')

# Quelques caractères non-BMP
a += u'\U00010000\U0010FFFF'.encode('utf8').decode('latin1')

print repr(a)

# Séquences de points de code Unicode ressemblant à des séquences UTF-8.
p = re.compile(ur'''(?x)
    \xF0[\x90-\xBF][\x80-\xBF]{2} |  # Séquences valides de 4 octets
        [\xF1-\xF3][\x80-\xBF]{3} |
    \xF4[\x80-\x8F][\x80-\xBF]{2} |

    \xE0[\xA0-\xBF][\x80-\xBF]    |  # Séquences valides de 3 octets
        [\xE1-\xEC][\x80-\xBF]{2} |
    \xED[\x80-\x9F][\x80-\xBF]    |
        [\xEE-\xEF][\x80-\xBF]{2} |

    [\xC2-\xDF][\x80-\xBF]           # Séquences valides de 2 octets
    ''')

def replace(m):
    return m.group(0).encode('latin1').decode('utf8')

print
print repr(p.sub(replace,a))

Résultat

u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439 \xd0\xb5\xd0\xba !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\x7f\xc2\x80\xdf\xbf\xe0\xa0\x80\xf0\x90\x80\x80\xf4\x8f\xbf\xbf'

u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439 \u0435\u043a !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\x7f\x80\u07ff\u0800\U00010000\U0010ffff'

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