Pregunta Secuencias de escape de proceso en una cadena en Python


A veces, cuando recibo información de un archivo o del usuario, aparece una cadena con secuencias de escape. Me gustaría procesar las secuencias de escape de la misma manera que Python procesa secuencias de escape en literales de cadenas.

Por ejemplo, digamos myString Se define como:

>>> myString = "spam\\neggs"
>>> print(myString)
spam\neggs

Quiero una función (la llamaré process) que hace esto:

>>> print(process(myString))
spam
eggs

Es importante que la función pueda procesar todas las secuencias de escape en Python (enumeradas en una tabla en el enlace anterior).

¿Tiene Python una función para hacer esto?


75
2017-10-26 03:43


origen


Respuestas:


Lo correcto es usar el código 'string-escape' para decodificar la cadena.

>>> myString = "spam\\neggs"
>>> decoded_string = bytes(myString, "utf-8").decode("unicode_escape") # python3 
>>> decoded_string = myString.decode('string_escape') # python2
>>> print(decoded_string)
spam
eggs

No use AST o eval. Usar los códecs de cuerda es mucho más seguro.


106
2017-10-26 05:01



unicode_escape no funciona en general

Resulta que el string_escape o unicode_escape la solución no funciona en general, particularmente, no funciona en presencia de Unicode real.

Si puedes estar seguro de que cada se escapará el carácter no ASCII (y recuerde, cualquier cosa más allá de los primeros 128 caracteres no es ASCII), unicode_escape hará lo correcto para ti. Pero si ya hay algunos caracteres literales que no son ASCII en su cadena, las cosas saldrán mal.

unicode_escape está fundamentalmente diseñado para convertir bytes en texto Unicode. Pero en muchos lugares, por ejemplo, el código fuente de Python, la fuente de datos ya es 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, por lo 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 el mismo problema existe 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 usar 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.)

los unicode_escape el códec, a pesar de su nombre, da por supuesto 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 te limita a los 256 caracteres latinos-1, ¡como si nunca hubiera sido inventado Unicode!

>>> 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 solo el unicode_escape decodificador de cosas que estamos seguros que serán texto ASCII. En particular, podemos asegurarnos de aplicarlo únicamente a las secuencias de escape de Python válidas, que se garantiza que son texto ASCII.

El plan es, encontraremos secuencias de escape usando una expresión regular, y usaremos una función como argumento para re.sub para reemplazarlos con su valor sin guardar.

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

75
2017-07-01 21:12



La respuesta realmente correcta y conveniente para python 3:

>>> import codecs
>>> myString = "spam\\neggs"
>>> print(codecs.escape_decode(bytes(myString, "utf-8"))[0].decode("utf-8"))
spam
eggs
>>> myString = "naïve \\t test"
>>> print(codecs.escape_decode(bytes(myString, "utf-8"))[0].decode("utf-8"))
naïve    test

Detalles sobre codecs.escape_decode:

  • codecs.escape_decode es un decodificador de bytes a bytes
  • codecs.escape_decode decodifica ascii secuencias de escape, tales como: b"\\n" -> b"\n", b"\\xce" -> b"\xce".
  • codecs.escape_decode no le importa o necesita saber sobre la codificación del objeto byte, pero la codificación de los bytes escapados debe coincidir con la codificación del resto del objeto.

Fondo:

  • @rspeer es correcto: unicode_escape es la solución incorrecta para python3. Esto es porque unicode_escape decodifica los bytes escapados, luego decodifica los bytes a cadena unicode, pero no recibe información con respecto a qué códec usar para la segunda operación.
  • @Jubo es correcto: evite el AST o eval.
  • Descubrí por primera vez codecs.escape_decode de esta respuesta a "¿cómo puedo .decode ('string-escape') en Python3?". Como dice esa respuesta, esa función actualmente no está documentada para python 3.

13
2018-05-05 20:27



los ast.literal_eval la función se acerca, pero esperará que la cadena se cotice correctamente primero.

Por supuesto, la interpretación de Python de los backslash escapes depende de cómo se cita la cadena ("" vs r"" vs u"", comillas triples, etc.) por lo que es posible que desee envolver la entrada del usuario en comillas adecuadas y pasar a literal_eval. Envolverlo entre comillas también evitará literal_eval de devolver un número, tupla, diccionario, etc.

Las cosas aún pueden ser complicadas si el usuario escribe citas sin comillas del tipo que pretende envolver alrededor de la cadena.


5
2017-10-26 03:50



Debajo del código debería funcionar \ n para mostrarse en la cadena.

import string

our_str = 'The String is \\n, \\n and \\n!'
new_str = string.replace(our_str, '/\\n', '/\n', 1)
print(new_str)

0
2018-03-26 09:42



Si confías en la fuente de los datos, solo dales una bofetada y evalúa ()?

>>> myString = 'spam\\neggs'
>>> print eval('"' + myString.replace('"','') + '"')
spam
eggs

PD. adicionada contramedida mal-código-exec - ahora va a despojar a todos " antes de evaluar


-4
2017-10-26 03:49