unicode_escape
não funciona em geral
Acontece que a solução string_escape
ou unicode_escape
não funciona em geral - particularmente, não funciona na presença de Unicode real.
Se você puder ter certeza de que todos os caracteres não ASCII terão escape (e lembre-se, qualquer coisa além dos primeiros 128 caracteres não são ASCII), unicode_escape
fará a coisa certa para você. Mas se já houver caracteres não-ASCII literais em sua string, as coisas darão errado.
unicode_escape
é fundamentalmente projetado para converter bytes em texto Unicode. Mas em muitos lugares - por exemplo, código-fonte Python - os dados-fonte já são texto Unicode.
A única maneira de funcionar corretamente é codificar o texto em bytes primeiro. UTF-8 é a codificação sensata para todo o texto, então deve funcionar, certo?
Os exemplos a seguir estão em Python 3, de modo que os literais de string são mais limpos, mas o mesmo problema existe com manifestações ligeiramente diferentes em Python 2 e 3.
>>> s = 'naïve \\t test'
>>> print(s.encode('utf-8').decode('unicode_escape'))
naïve test
Bem, isso está errado.
A nova maneira recomendada de usar codecs que decodificam texto em texto é chamar codecs.decode
diretamente. Isso ajuda?
>>> import codecs
>>> print(codecs.decode(s, 'unicode_escape'))
naïve test
De modo nenhum. (Além disso, o acima é um UnicodeError no Python 2.)
O unicode_escape
codec, apesar do nome, supõe que todos os bytes não ASCII estão na codificação Latin-1 (ISO-8859-1). Então você teria que fazer assim:
>>> print(s.encode('latin-1').decode('unicode_escape'))
naïve test
Mas isso é terrível. Isso limita você aos 256 caracteres Latin-1, como se o Unicode nunca tivesse sido 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)
Adicionando uma expressão regular para resolver o problema
(Surpreendentemente, não temos agora dois problemas.)
O que precisamos fazer é apenas aplicar o unicode_escape
decodificador a coisas que temos certeza que são texto ASCII. Em particular, podemos ter certeza de aplicá-lo apenas a sequências de escape válidas do Python, que são garantidamente texto ASCII.
O plano é encontrar sequências de escape usando uma expressão regular e usar uma função como o argumento re.sub
para substituí-las por seu valor sem 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)
E com isso:
>>> print(decode_escapes('Ernő \\t Rubik'))
Ernő Rubik
'spam'+"eggs"+'''some'''+"""more"""
seja processada?