unicode_escape
no funciona en general
Resulta que la solución string_escape
o unicode_escape
no funciona en general; en particular, no funciona en presencia de Unicode real.
Si puede estar seguro de que todos los caracteres que no sean ASCII se escaparán (y recuerde, cualquier cosa más allá de los primeros 128 caracteres no es ASCII), unicode_escape
hará lo correcto por usted. Pero si ya hay caracteres literales no ASCII en su cadena, las cosas saldrán mal.
unicode_escape
está diseñado fundamentalmente para convertir bytes en texto Unicode. Pero en muchos lugares, por ejemplo, el código fuente de Python, los datos de origen ya son texto Unicode.
La única forma en que esto puede funcionar correctamente es si primero codifica el texto en bytes. UTF-8 es la codificación sensata para todo el texto, así que debería funcionar, ¿verdad?
Los siguientes ejemplos están en Python 3, por lo que los literales de cadena son más limpios, pero existe el mismo problema con manifestaciones ligeramente diferentes en Python 2 y 3.
>>> s = 'naïve \\t test'
>>> print(s.encode('utf-8').decode('unicode_escape'))
naïve test
Bueno, eso está mal.
La nueva forma recomendada de utilizar códecs que decodifican texto en texto es llamar codecs.decode
directamente. ¿Eso ayuda?
>>> import codecs
>>> print(codecs.decode(s, 'unicode_escape'))
naïve test
De ningún modo. (Además, lo anterior es un UnicodeError en Python 2.)
El unicode_escape
códec, a pesar de su nombre, asume que todos los bytes que no son ASCII están en la codificación Latin-1 (ISO-8859-1). Entonces tendrías que hacerlo así:
>>> print(s.encode('latin-1').decode('unicode_escape'))
naïve test
Pero eso es terrible. Esto lo limita a los 256 caracteres Latin-1, ¡como si Unicode nunca se hubiera inventado!
>>> print('Ernő \\t Rubik'.encode('latin-1').decode('unicode_escape'))
UnicodeEncodeError: 'latin-1' codec can't encode character '\u0151'
in position 3: ordinal not in range(256)
Agregar una expresión regular para resolver el problema
(Sorprendentemente, ahora no tenemos dos problemas).
Lo que tenemos que hacer es aplicar el unicode_escape
decodificador solo a cosas que estamos seguros de que serán texto ASCII. En particular, podemos asegurarnos de aplicarlo solo a secuencias de escape de Python válidas, que están garantizadas como texto ASCII.
El plan es que encontraremos secuencias de escape usando una expresión regular y usaremos una función como argumento re.sub
para reemplazarlas con su valor sin escape.
import re
import codecs
ESCAPE_SEQUENCE_RE = re.compile(r'''
( \\U........ # 8-digit hex escapes
| \\u.... # 4-digit hex escapes
| \\x.. # 2-digit hex escapes
| \\[0-7]{1,3} # Octal escapes
| \\N\{[^}]+\} # Unicode characters by name
| \\[\\'"abfnrtv] # Single-character escapes
)''', re.UNICODE | re.VERBOSE)
def decode_escapes(s):
def decode_match(match):
return codecs.decode(match.group(0), 'unicode-escape')
return ESCAPE_SEQUENCE_RE.sub(decode_match, s)
Y con eso:
>>> print(decode_escapes('Ernő \\t Rubik'))
Ernő Rubik
'spam'+"eggs"+'''some'''+"""more"""
procesara una cadena que contiene ?