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?
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.
(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ää
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.
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:
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))
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"
Vous devez convertir unichr
s en chr
s, 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'
utf8
octets codés en tant que points de code Unicode (c'est le PROBLÈME)Si je me trompe, dites-le moi.