Conversión de doble barra diagonal utf-8

¡No puedo conseguir que esto funcione! Tengo un archivo de texto de un analizador de archivos de juegos guardados con un montón de nombres chinos UTF-8 en forma de byte, como este en source.txt:

\ xe6 \ x89 \ x8e \ xe5 \ x8a \ xa0 \ xe6 \ x8b \ x89

Pero, no importa cómo lo importe en Python (3 o 2), obtengo esta cadena, en el mejor de los casos:

\\ xe6 \\ x89 \\ x8e \\ xe5 \\ x8a \\ xa0 \\ xe6 \\ x8b \\ x89

He intentado, como han sugerido otros subprocesos, volver a codificar la cadena como UTF-8 y luego decodificarla con escape Unicode, así:

stringName.encode("utf-8").decode("unicode_escape") 

Pero luego desordena la encoding original, y da esto como la cadena:

‘æ \ x89 \ x8eå \ x8a \ xa0æ \ x8b \ x89’ (la impresión de esta cadena da como resultado: æå æ)

Ahora, si copio y pego manualmente b + la cadena original en el nombre del archivo y lo codifico, obtengo la encoding correcta. Por ejemplo:

 b'\xe6\x89\x8e\xe5\x8a\xa0\xe6\x8b\x89'.encode("utf-8") 

Resultados en: ‘扎加拉’

Pero, no puedo hacer esto programáticamente. Ni siquiera puedo deshacerme de las barras dobles.

Para que quede claro, source.txt contiene barras invertidas únicas. He intentado importarlo de muchas maneras, pero este es el más común:

 with open('source.txt','r',encoding='utf-8') as f_open: source = f_open.read() 

Bien, entonces hice clic en la respuesta de abajo (creo), pero esto es lo que funciona:

 from ast import literal_eval decodedString = literal_eval("b'{}'".format(stringVariable)).decode('utf-8') 

No puedo usarlo en todo el archivo debido a otros problemas de encoding, pero al extraer cada nombre como una cadena (stringVariable) ¡y luego hacerlo funciona! ¡Gracias!

Para ser más claros, el archivo original no es solo estas codificaciones utf desordenadas. Solo los usa para ciertos campos. Por ejemplo, aquí está el comienzo del archivo:

 {'m_cacheHandles': ['s2ma\x00\x00CN\x1f\x1b"\x8d\xdb\x1fr \\\xbf\xd4D\x05R\x87\x10\x0b\x0f9\x95\x9b\xe8\x16T\x81b\xe4\x08\x1e\xa8U\x11', 's2ma\x00\x00CN\x1a\xd9L\x12n\xb9\x8aL\x1d\xe7\xb8\xe6\xf8\xaa\xa1S\xdb\xa5+\t\xd3\x82^\x0c\x89\xdb\xc5\x82\x8d\xb7\x0fv', 's2ma\x00\x00CN\x92\xd8\x17D\xc1D\x1b\xf6(\xedj\xb7\xe9\xd1\x94\x85\xc8`\x91M\x8btZ\x91\xf65\x1f\xf9\xdc\xd4\xe6\xbb', 's2ma\x00\x00CN\xa1\xe9\xab\xcd?\xd2PS\xc9\x03\xab\x13R\xa6\x85u7(K2\x9d\x08\xb8k+\xe2\xdeI\xc3\xab\x7fC', 's2ma\x00\x00CNN\xa5\xe7\xaf\xa0\x84\xe5\xbc\xe9HX\xb93S*sj\xe3\xf8\xe7\x84`\xf1Ye\x15~\xb93\x1f\xc90', 's2ma\x00\x00CN8\xc6\x13F\x19\x1f\x97AH\xfa\x81m\xac\xc9\xa6\xa8\x90s\xfdd\x06\rL]z\xbb\x15\xdcI\x93\xd3V'], 'm_campaignIndex': 0, 'm_defaultDifficulty': 7, 'm_description': '', 'm_difficulty': '', 'm_gameSpeed': 4, 'm_imageFilePath': '', 'm_isBlizzardMap': True, 'm_mapFileName': '', 'm_miniSave': False, 'm_modPaths': None, 'm_playerList': [{'m_color': {'m_a': 255, 'm_b': 255, 'm_g': 92, 'm_r': 36}, 'm_control': 2, 'm_handicap': 0, 'm_hero': '\xe6\x89\x8e\xe5\x8a\xa0\xe6\x8b\x89', 

Toda la información antes del campo ‘m_hero’: no ​​es utf-8. Entonces, usar la solución de ShadowRanger funciona si el archivo solo está formado por estas falsas codificaciones de utf, pero no funciona cuando ya he analizado m_hero como una cadena e bash convertirlo. La solución de Karin funciona para eso.

Supongo que estás usando Python 3. En Python 2, las cadenas son bytes de forma predeterminada, así que solo funcionaría para ti. Pero en Python 3, las cadenas son unicode y se interpretan como unicode, que es lo que hace que este problema sea más difícil si tiene una cadena de bytes que se lee como unicode.

Esta solución fue inspirada por la respuesta de mgilson. Podemos literalmente evaluar su cadena de Unicode como una cadena de bytes usando literal_eval :

 from ast import literal_eval with open('source.txt', 'r', encoding='utf-8') as f_open: source = f_open.read() string = literal_eval("b'{}'".format(source)).decode('utf-8') print(string) # 扎加拉 

El problema es que el códec de unicode_escape está decodificando implícitamente el resultado de las correcciones de escape suponiendo que los bytes son latin-1 , no utf-8 . Puedes arreglar esto por:

 # Read the file as bytes: with open(myfile, 'rb') as f: data = f.read() # Decode with unicode-escape to get Py2 unicode/Py3 str, but interpreted # incorrectly as latin-1 badlatin = data.decode('unicode-escape') # Encode back as latin-1 to get back the raw bytes (it's a 1-1 encoding), # then decode them properly as utf-8 goodutf8 = badlatin.encode('latin-1').decode('utf-8') 

Lo que (suponiendo que el archivo contenga las barras invertidas y los códigos literales, no los bytes que representan) te deja con '\u624e\u52a0\u62c9' (lo que debería ser correcto, solo estoy en un sistema sin soporte de fuente para esos caracteres, por lo que eso es sólo la repr segura basada en escapes de Unicode). Puede omitir un paso en Py2 utilizando el códec de string-escape para la primera decode (lo que creo que le permitiría omitir el paso .encode('latin-1') ), pero esta solución debería ser portátil, y El costo no debería ser terrible.

Puedes hacer algunas cosas tontas como evaluar la cadena:

 import ast s = r'\xe6\x89\x8e\xe5\x8a\xa0\xe6\x8b\x89' print ast.literal_eval('"%s"' % s).decode('utf-8') 
  • nota use ast.literal_eval si no quiere que los atacantes obtengan acceso a su sistema 😛

Usar esto en tu caso probablemente se vería como:

 with open('file') as file_handle: data = ast.literal_eval('"%s"' % file.read()).decode('utf-8') 

Creo que el problema real aquí es probable que tenga un archivo que contenga cadenas que representan bytes (en lugar de tener un archivo que solo almacena los bytes). Por lo tanto, corregir el código generado en ese archivo en primer lugar es probablemente una mejor opción. Sin embargo, a menos que esto sea lo mejor que se me ocurra …

Solución en Python3 con solo manipulaciones de cadenas y conversiones de encoding sin mala eval 🙂

 import binascii str = '\\xe6\\x89\\x8e\\xe5\\x8a\\xa0\\xe6\\x8b\\x89' str = str.replace('\\x', '') # str == 'e6898ee58aa0e68b89' # we can use any encoding as long as it translate ascii as is, # for example we can do str.encode('ascii') here str = str.encode('utf8') # str == b'e6898ee58aa0e68b89' str = binascii.a2b_hex(str) # str == b'\xe6\x89\x8e\xe5\x8a\xa0\xe6\x8b\x89' str = str.decode('utf8') # str == '扎加拉' 

Si te gusta una sola línea, entonces podemos ponerlo simplemente como:

 binascii.a2b_hex(str.replace('\\x', '').encode()).decode('utf8') 

Al final del día, lo que recibes es una cadena, ¿no? Yo usaría el método string.replace para convertir la barra doble en una barra simple y agregar el prefijo b para que funcione.

Así que hay varias formas diferentes de interpretar que los datos están “en forma de byte”. Supongamos que realmente haces:

 s = b'\xe6\x89\x8e\xe5\x8a\xa0\xe6\x8b\x89' 

El prefijo b indica que son bytes. Sin meterse en todo el lío que es bytes frente a puntos de código / caracteres y las largas diferencias entre Python 2 y 3, la cadena con prefijo b indica que se pretende que sean bytes (por ejemplo, bytes UTF-8 sin procesar).

Luego simplemente descodifíquelo, que convierte la encoding UTF-8 (que ya tiene en los bytes, en verdaderos caracteres Unicode. En Python 2.7, por ejemplo:

 print s.decode('utf-8') 

rendimientos

 扎加拉 

Uno de sus ejemplos hizo una encoding seguida de una desencoding, que solo puede llevar a la tristeza y al dolor. Si su variable contiene bytes UTF-8 verdaderos, solo necesita la deencoding.

Actualización Según el análisis, parece que los datos no están realmente en bytes UTF-8, sino en una versión serializada de la misma cadena. Hay muchas formas de pasar de serie en serie a bytes. Aquí está el mío:

 from struct import pack def byteize(s): """ Given a backslash-escaped string serialization of bytes, decode it into a genuine byte string. """ bvals = [int(s[i:i+2], 16) for i in range(2, len(s), 4)] return pack(str(len(bvals)) + 'B', *bvals) 

Entonces:

 print byteize(s).decode('utf-8') 

como antes los rendimientos:

 扎加拉 

Este byteize() no es tan general como la respuesta aceptada basada en literal_eval() , pero la %timeit comparativa %timeit muestra que es aproximadamente un 33% más rápida en cadenas cortas. Podría acelerarse aún más cambiando el range por xrange bajo Python 2. El enfoque literal_eval gana fácilmente en cadenas largas, sin embargo, dada su naturaleza de nivel inferior.

 100000 loops, best of 3: 6.19 µs per loop 100000 loops, best of 3: 8.3 µs per loop