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?


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.


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

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.

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.


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 ()?


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)

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.