Elabora sequenze di escape in una stringa in Python


112

A volte, quando ricevo input da un file o dall'utente, ottengo una stringa con sequenze di escape al suo interno. Vorrei elaborare le sequenze di escape nello stesso modo in cui Python elabora le sequenze di escape in stringhe letterali .

Ad esempio, diciamo che myStringè definito come:

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

Voglio una funzione (la chiamerò process) che faccia questo:

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

È importante che la funzione possa elaborare tutte le sequenze di escape in Python (elencate in una tabella nel collegamento sopra).

Python ha una funzione per farlo?


1
hmmm, come ti aspetteresti esattamente che una stringa contenente 'spam'+"eggs"+'''some'''+"""more"""venga elaborata?
Nas Banov

@Nas Banov Questo è un buon test. Quella stringa non contiene sequenze di escape, quindi dovrebbe essere esattamente la stessa dopo l'elaborazione. myString = "'spam'+\"eggs\"+'''some'''+\"\"\"more\"\"\"", print(bytes(myString, "utf-8").decode("unicode_escape"))sembra funzionare.
dln385

5
La maggior parte delle risposte a questa domanda presenta seri problemi. Non sembra esserci un modo standard per onorare le sequenze di escape in Python senza rompere l'unicode. La risposta pubblicata da @rspeer è quella che ho adottato per Grako poiché finora gestisce tutti i casi noti.
Apalala

Risposte:


138

La cosa corretta da fare è utilizzare il codice "string-escape" per decodificare la stringa.

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

Non utilizzare AST o eval. L'uso dei codec di stringa è molto più sicuro.


3
senza dubbio, la soluzione migliore ! btw, dai documenti dovrebbe essere "string_escape" (con trattino basso) ma per qualche motivo accetta qualsiasi cosa nel pattern 'string escape', 'string @ escape "e quant'altro ... fondamentalmente'string\W+escape'
Nas Banov

2
@Nas Banov La documentazione fa una piccola menzione a riguardo :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
Questa soluzione non è abbastanza buona perché non gestisce il caso in cui sono presenti caratteri Unicode legittimi nella stringa originale. Se ci provi: >>> print("juancarlo\\tañez".encode('utf-8').decode('unicode_escape')) ottieni: juancarlo añez
Apalala

2
D'accordo con @Apalala: questo non è abbastanza buono. Dai un'occhiata alla risposta di rseeper di seguito per una soluzione completa che funziona in Python2 e 3!
Christian Aichinger

2
Poiché latin1si presume da unicode_escape, rifare il bit di codifica / decodifica, ad esempios.encode('utf-8').decode('unicode_escape').encode('latin1').decode('utf8')
metatoaster

121

unicode_escape non funziona in generale

Si scopre che la soluzione string_escapeo unicode_escapenon funziona in generale, in particolare, non funziona in presenza di Unicode effettivo.

Se puoi essere sicuro che ogni carattere non ASCII verrà sottoposto a escape (e ricorda, qualsiasi cosa oltre i primi 128 caratteri non è ASCII), unicode_escapefarà la cosa giusta per te. Ma se nella stringa sono già presenti caratteri letterali non ASCII, le cose andranno storte.

unicode_escapeè progettato fondamentalmente per convertire i byte in testo Unicode. Ma in molti punti, ad esempio il codice sorgente Python, i dati di origine sono già testo Unicode.

L'unico modo in cui questo può funzionare correttamente è se codifichi prima il testo in byte. UTF-8 è la codifica sensata per tutto il testo, quindi dovrebbe funzionare, giusto?

I seguenti esempi sono in Python 3, in modo che le stringhe letterali siano più pulite, ma lo stesso problema esiste con manifestazioni leggermente diverse sia su Python 2 che su 3.

>>> s = 'naïve \\t test'
>>> print(s.encode('utf-8').decode('unicode_escape'))
naïve   test

Beh, è ​​sbagliato.

Il nuovo modo consigliato per utilizzare i codec che decodificano il testo in testo è chiamare codecs.decodedirettamente. Questo aiuta?

>>> import codecs
>>> print(codecs.decode(s, 'unicode_escape'))
naïve   test

Affatto. (Inoltre, quanto sopra è un UnicodeError su Python 2.)

Il unicode_escapecodec, nonostante il nome, risulta presumere che tutti i byte non ASCII siano nella codifica Latin-1 (ISO-8859-1). Quindi dovresti farlo in questo modo:

>>> print(s.encode('latin-1').decode('unicode_escape'))
naïve    test

Ma è terribile. Questo ti limita a 256 caratteri Latin-1, come se Unicode non fosse mai stato inventato!

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

Aggiunta di un'espressione regolare per risolvere il problema

(Sorprendentemente, ora non abbiamo due problemi.)

Quello che dobbiamo fare è applicare il unicode_escapedecodificatore solo a cose che siamo certi saranno testo ASCII. In particolare, possiamo assicurarci di applicarlo solo a sequenze di escape Python valide, che sono garantite come testo ASCII.

Il piano è che troveremo sequenze di escape utilizzando un'espressione regolare e utilizzeremo una funzione come argomento re.subper sostituirle con il loro valore senza caratteri di 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 con quello:

>>> print(decode_escapes('Ernő \\t Rubik'))
Ernő     Rubik

2
abbiamo bisogno di tipi più comprensivi di risposte come questa. Grazie.
v.oddou

Funziona con os.septutto questo? Sto provando a farlo: patt = '^' + self.prefix + os.sep ; name = sub(decode_escapes(patt), '', name)e non funziona. Il punto e virgola è presente al posto di una nuova riga.
Pureferret

@Pureferret Non sono proprio sicuro di quello che stai chiedendo, ma probabilmente non dovresti eseguirlo su stringhe in cui la barra rovesciata ha un significato diverso, come i percorsi dei file di Windows. (È questo quello che sei os.sep?) Se hai sequenze di escape con barra rovesciata nei nomi delle directory di Windows, la situazione è praticamente irrecuperabile.
rspeer

La sequenza di escape non contiene escape, ma ricevo un errore di "stringa di escape fasulla"
Pureferret,

Che mi dice che si è conclusa qualche altra espressione regolare con un backslash: stackoverflow.com/questions/4427174/...
rspeer

33

La risposta effettivamente corretta e conveniente per 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

Dettagli riguardanti codecs.escape_decode:

  • codecs.escape_decode è un decodificatore da byte a byte
  • codecs.escape_decodedecodifica le sequenze di escape ASCII, come: b"\\n"-> b"\n", b"\\xce"-> b"\xce".
  • codecs.escape_decode non si preoccupa o ha bisogno di sapere della codifica dell'oggetto byte, ma la codifica dei byte con escape dovrebbe corrispondere alla codifica del resto dell'oggetto.

Sfondo:

  • @rspeer è corretto: unicode_escapeè la soluzione sbagliata per python3. Questo perché unicode_escapedecodifica i byte con escape, quindi decodifica i byte in una stringa Unicode, ma non riceve informazioni su quale codec utilizzare per la seconda operazione.
  • @Jerub è corretto: evita AST o eval.
  • Ho scoperto per la prima volta codecs.escape_decodeda questa risposta a "come faccio a .decode ('string-escape') in Python3?" . Come afferma la risposta, quella funzione non è attualmente documentata per python 3.

Questa è la vera risposta (: Peccato che si
basi

5
Questa è la risposta per le situazioni in cui le sequenze di escape che hai sono escape \xdi byte UTF-8. Ma poiché decodifica i byte in byte, non decodifica e non può decodificare gli escape di caratteri Unicode non ASCII, come gli \uescape.
rspeer

Solo per tua informazione, questa funzione non è tecnicamente pubblica. vedi bugs.python.org/issue30588
Hack5

8

La ast.literal_evalfunzione si avvicina, ma si aspetta che la stringa venga citata correttamente per prima.

Ovviamente l'interpretazione di Python degli escape con barra rovesciata dipende da come la stringa viene citata ( ""vs r""vs u"", virgolette triple, ecc.) Quindi potresti voler racchiudere l'input dell'utente tra virgolette adatte e passare a literal_eval. Racchiudendolo tra virgolette eviterà anche literal_evaldi restituire un numero, una tupla, un dizionario, ecc.

Le cose potrebbero ancora diventare complicate se l'utente digita virgolette non quotate del tipo che si intende racchiudere nella stringa.


Vedo. Questo sembra essere potenzialmente pericolosi come dici tu: myString = "\"\ndoBadStuff()\n\"", print(ast.literal_eval('"' + myString + '"'))sembra cercare di eseguire il codice. In che modo è ast.literal_evaldiverso / più sicuro di eval?
dln385

5
@ dln385: literal_evalnon esegue mai il codice. Dalla documentazione, "Questo può essere utilizzato per valutare in modo sicuro stringhe contenenti espressioni Python da fonti non attendibili senza la necessità di analizzare i valori da soli."
Greg Hewgill,

2

Questo è un brutto modo di farlo, ma ha funzionato per me quando cercavo di interpretare gli ottali con escape passati in un argomento stringa.

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

Vale la pena ricordare che c'è una differenza tra eval e ast.literal_eval (eval è molto più pericoloso). Vedi Usare eval () di python vs. ast.literal_eval ()?


0

Il codice seguente dovrebbe funzionare perché \ n deve essere visualizzato sulla stringa.

import string

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

1
Questo non funziona come scritto (le barre in avanti replacenon fanno nulla), utilizza API completamente obsolete (le stringfunzioni del modulo di questo tipo sono deprecate a partire da Python 2.0, sostituite dai strmetodi e completamente eliminate in Python 3), e solo gestisce il caso specifico di sostituzione di una singola nuova riga, non l'elaborazione di escape generale.
ShadowRanger
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.