web-dev-qa-db-fra.com

Octets dans un unicode Python

Dans Python 2, les chaînes Unicode peuvent contenir à la fois unicode et des octets:

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

Je comprends que c'est absolument pas quelque chose que l'on devrait écrire dans son propre code, mais c'est une chaîne que je dois gérer.

Les octets de la chaîne ci-dessus sont 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 aux rendements UTF-8

>>> 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'

Ce qui a ensuite été décodé depuis UTF-8 donne la chaîne initiale avec des octets, 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 hacky de 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'
# Almost there, the bytes are proper now but the former real-unicode characters
# are now escaped with \u's; need to un-escape them.
>>> 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' # Success!

Cela fonctionne bien mais semble très hacky en raison de son utilisation de eval, repr, puis d'une expression régulière supplémentaire de la représentation de chaîne unicode. Existe-t-il un moyen plus propre?

33
Etienne Perot

Dans Python 2, les chaînes Unicode peuvent contenir à la fois unicode et des octets:

Non, ils ne le peuvent pas. Ils contiennent des caractères Unicode.

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

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

Cependant, si vous supposez que vous pouvez toujours interpréter ces valeurs comme des valeurs codées, vous pouvez essayer d'écrire quelque chose qui analyse tour à tour chaque caractère (utilisez ord pour convertir en un entier codé), décode les caractères <256 comme UTF-8 et transmet les caractères> = 256 tels qu'ils étaient.

22
Karl Knechtel

(En réponse aux commentaires ci-dessus): ce code convertit tout ce qui ressemble à 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ää  
12
georg

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

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

Mélange la représentation interne de python des chaînes unicode avec utf-8 texte codé. Si nous considérons simplement 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 utf-8 codé:

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

Faux! Mais qu'en est-il:

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

Hourra.

Donc. Qu'est-ce que cela signifie 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 ce formulaire pour commencer et comment l'éviter/le corriger avant vous les avoir tous mélangés.

11
beerbajay

Vous avez déjà une réponse, mais voici un moyen de déchiffrer UTF-8 - comme Les séquences Unicode qui sont moins susceptibles de décoder les séquences Unicode latin-1 par erreur. Le re.sub une fonction:

  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 dans sa séquence équivalente latin-1 octet.
  3. Décode la séquence utilisant UTF-8 en Unicode.
  4. Remplace la séquence d'origine UTF-8 par le caractère Unicode correspondant.

Notez que cela pourrait toujours correspondre à une séquence Unicode si juste les bons caractères apparaissent côte à côte, mais c'est beaucoup moins probable.

import re

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

# printable Unicode characters < 256.
a += ''.join(chr(n) for n in range(32,256)).decode('latin1')

# a few UTF-8 characters decoded as latin1.
a += ''.join(unichr(n) for n in [2**7-1,2**7,2**11-1,2**11]).encode('utf8').decode('latin1')

# Some non-BMP characters
a += u'\U00010000\U0010FFFF'.encode('utf8').decode('latin1')

print repr(a)

# Unicode codepoint sequences that resemble UTF-8 sequences.
p = re.compile(ur'''(?x)
    \xF0[\x90-\xBF][\x80-\xBF]{2} |  # Valid 4-byte sequences
        [\xF1-\xF3][\x80-\xBF]{3} |
    \xF4[\x80-\x8F][\x80-\xBF]{2} |

    \xE0[\xA0-\xBF][\x80-\xBF]    |  # Valid 3-byte sequences
        [\xE1-\xEC][\x80-\xBF]{2} |
    \xED[\x80-\x9F][\x80-\xBF]    |
        [\xEE-\xEF][\x80-\xBF]{2} |

    [\xC2-\xDF][\x80-\xBF]           # Valid 2-byte sequences
    ''')

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

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

Production

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"

5
Mark Tolonen

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

u'\xd0' == u'\u00d0' est True

$ 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]*' correspondra à unichrs u'[\u0000-\u00ff]*'
  • u'\xd0\xb5\xd0\xba' == u'\u00d0\u00b5\u00d0\u00ba'
  • Tu utilises utf8 octets codés en tant que points de code Unicode (c'est le PROBLÈME)
  • Je résous le problème en prétendant que ces unichars erronés sont les octets correspondants
  • Je recherche tous ces unichars erronés, je les convertis en caractères, puis je les décode.

Si je me trompe, dites-le moi.

5
kev