Processar sequências de escape em uma string em Python

112

Às vezes, quando recebo a entrada de um arquivo ou do usuário, recebo uma string com sequências de escape. Gostaria de processar as sequências de escape da mesma forma que o Python processa as sequências de escape em literais de string .

Por exemplo, digamos que myStringseja definido como:

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

Quero uma função (vou chamá-la process) que faça isso:

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

É importante que a função possa processar todas as sequências de escape em Python (listadas em uma tabela no link acima).

O Python tem uma função para fazer isso?

dln385
fonte
1
hmmm, como exatamente você espera que uma string contendo 'spam'+"eggs"+'''some'''+"""more"""seja processada?
Nas Banov
@Nas Banov Esse é um bom teste. Essa string não contém sequências de escape, portanto, deve ser exatamente a mesma após o processamento. myString = "'spam'+\"eggs\"+'''some'''+\"\"\"more\"\"\"", print(bytes(myString, "utf-8").decode("unicode_escape"))parece funcionar.
dln385
5
A maioria das respostas a esta pergunta tem problemas sérios. Parece não haver uma maneira padrão de honrar as sequências de escape em Python sem quebrar o Unicode. A resposta postada por @rspeer é a mesma que adotei para Grako , pois até agora ele trata de todos os casos conhecidos.
Apalala 01 de

Respostas:

138

A coisa correta a fazer é usar o código 'string-escape' para decodificar a string.

>>> 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

Não use o AST ou eval. Usar os codecs de string é muito mais seguro.

Jerub
fonte
3
mãos para baixo, a melhor solução! btw, pelos documentos deve ser "string_escape" (com sublinhado), mas por alguma razão aceita qualquer coisa no padrão 'string escape', 'string @ escape "e outros enfeites ... basicamente'string\W+escape'
Nas Banov
2
@Nas Banov A documentação faz uma pequena menção sobre isso :Notice that spelling alternatives that only differ in case or use a hyphen instead of an underscore are also valid aliases; therefore, e.g. 'utf-8' is a valid alias for the 'utf_8' codec.
dln385
30
Essa solução não é boa o suficiente porque não trata do caso em que há caracteres Unicode legítimos na string original. Se você tentar: >>> print("juancarlo\\tañez".encode('utf-8').decode('unicode_escape')) Você obtém: juancarlo añez
Apalala 01 de
2
Concordo com @Apalala: isso não é bom o suficiente. Confira a resposta de rseeper abaixo para uma solução completa que funciona em Python2 e 3!
Christian Aichinger,
2
Como latin1é assumido por unicode_escape, refaça o bit de codificação / decodificação, por exemplos.encode('utf-8').decode('unicode_escape').encode('latin1').decode('utf8')
metatoaster
121

unicode_escape não funciona em geral

Acontece que a solução string_escapeou unicode_escapenã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_escapefará 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.decodediretamente. 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_escapecodec, 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_escapedecodificador 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.subpara 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
rspeer
fonte
2
precisamos de tipos de respostas mais abrangentes como essa. obrigado.
v.oddou
Isso funciona com os.septudo? Estou tentando fazer isso: patt = '^' + self.prefix + os.sep ; name = sub(decode_escapes(patt), '', name)e não está funcionando. O ponto-e-vírgula substitui uma nova linha.
Pureferret de
@Pureferret Não tenho certeza do que você está perguntando, mas você provavelmente não deve executar isso em strings onde a barra invertida tem um significado diferente, como caminhos de arquivo do Windows. (É isso o que você os.sepé?) Se você tiver as sequências de escape com barra invertida nos nomes de diretório do Windows, a situação é praticamente irrecuperável.
rspeer de
A sequência de escape não tem escapes, mas estou recebendo um erro de 'string de escape falsa'
Pureferret
Isso me diz que você encerrou alguma outra expressão regular com uma barra invertida: stackoverflow.com/questions/4427174/…
rspeer
33

A resposta realmente correta e 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

Detalhes sobre codecs.escape_decode:

  • codecs.escape_decode é um decodificador de bytes para bytes
  • codecs.escape_decodedecodifica sequências de escape ascii, como: b"\\n"-> b"\n", b"\\xce"-> b"\xce".
  • codecs.escape_decode não se importa ou precisa saber sobre a codificação do objeto de byte, mas a codificação dos bytes de escape deve corresponder à codificação do resto do objeto.

Fundo:

  • @rspeer está correto: unicode_escapeé a solução incorreta para python3. Isso ocorre porque unicode_escapedecodifica bytes de escape e, em seguida, decodifica bytes para string Unicode, mas não recebe nenhuma informação sobre qual codec usar para a segunda operação.
  • @Jerub está correto: evite o AST ou eval.
  • Eu descobri codecs.escape_decodepela primeira vez com esta resposta para "como faço para .decode ('string-escape') em Python3?" . Como afirma essa resposta, essa função não está documentada atualmente para Python 3.
user19087
fonte
Esta é a resposta real (: Que pena que depende de uma função mal documentada.
jwd
5
Esta é a resposta para situações em que as sequências de escape que você possui são \xescapes de bytes UTF-8. Mas como ele decodifica bytes em bytes, ele não decodifica - e não pode - decodificar quaisquer escapes de caracteres Unicode não ASCII, como \uescapes.
rspeer de
Apenas um FYI, esta função não é tecnicamente pública. consulte bugs.python.org/issue30588
Hack5
8

A ast.literal_evalfunção chega perto, mas espera que a string seja devidamente citada primeiro.

É claro que a interpretação do Python dos escapes de barra invertida depende de como a string está entre aspas ( ""vs r""vs u"", aspas triplas, etc), então você pode querer envolver a entrada do usuário em aspas adequadas e passar para literal_eval. Colocá-lo entre aspas também impedirá o literal_evalretorno de um número, tupla, dicionário, etc.

As coisas ainda podem ficar complicadas se o usuário digitar aspas sem aspas do tipo que você pretende envolver na string.

Greg Hewgill
fonte
Entendo. Este parece ser potencialmente perigoso como você diz: myString = "\"\ndoBadStuff()\n\"", print(ast.literal_eval('"' + myString + '"'))parece tentar executar código. Como é ast.literal_evaldiferente / mais seguro do que eval?
dln385
5
@ dln385: literal_evalnunca executa código. Na documentação, "Isso pode ser usado para avaliar com segurança strings que contêm expressões Python de fontes não confiáveis, sem a necessidade de analisar os valores por conta própria."
Greg Hewgill
2

Esta é uma maneira ruim de fazer isso, mas funcionou para mim ao tentar interpretar octais escapados passados ​​em um argumento de string.

input_string = eval('b"' + sys.argv[1] + '"')

Vale a pena mencionar que há uma diferença entre eval e ast.literal_eval (eval sendo muito mais inseguro). Consulte Usando eval () do python vs. ast.literal_eval ()?

LimeTr33
fonte
0

O código abaixo deve funcionar para \ n é necessário para ser exibido na string.

import string

our_str = 'The String is \\n, \\n and \\n!'
new_str = string.replace(our_str, '/\\n', '/\n', 1)
print(new_str)
Vignesh Ramsubbose
fonte
1
Isso não funciona como está escrito (as barras replacenão fazem nada), usa APIs totalmente desatualizadas (as stringfunções de módulo desse tipo foram descontinuadas no Python 2.0, substituídas pelos strmétodos e foram completamente para o Python 3), e apenas lida com o caso específico de substituição de uma única nova linha, não com o processamento de escape geral.
ShadowRanger