Verarbeiten Sie Escape-Sequenzen in einer Zeichenfolge in Python

112

Wenn ich Eingaben von einer Datei oder vom Benutzer erhalte, erhalte ich manchmal eine Zeichenfolge mit Escape-Sequenzen. Ich möchte die Escape-Sequenzen genauso verarbeiten wie Python Escape-Sequenzen in String-Literalen .

Angenommen, es myStringist definiert als:

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

Ich möchte eine Funktion (ich werde sie nennen process), die dies tut:

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

Es ist wichtig, dass die Funktion alle Escape-Sequenzen in Python verarbeiten kann (in einer Tabelle im obigen Link aufgeführt).

Hat Python eine Funktion, um dies zu tun?

dln385
quelle
1
hmmm, wie genau würden Sie erwarten, dass ein String mit 'spam'+"eggs"+'''some'''+"""more"""verarbeitet wird?
Nas Banov
@Nas Banov Das ist ein guter Test. Diese Zeichenfolge enthält keine Escape-Sequenzen, daher sollte sie nach der Verarbeitung genau gleich sein. myString = "'spam'+\"eggs\"+'''some'''+\"\"\"more\"\"\"", print(bytes(myString, "utf-8").decode("unicode_escape"))Scheint zu funktionieren.
dln385
5
Die meisten Antworten auf diese Frage haben ernsthafte Probleme. Es scheint keine Standardmethode zu geben, um Escape-Sequenzen in Python zu honorieren, ohne Unicode zu brechen. Die Antwort von @rspeer ist die, die ich für Grako übernommen habe, da sie bisher alle bekannten Fälle behandelt.
Apalala

Antworten:

137

Das Richtige ist, den 'String-Escape'-Code zu verwenden, um den String zu dekodieren.

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

Verwenden Sie nicht den AST oder die Bewertung. Die Verwendung der String-Codecs ist viel sicherer.

Jerub
quelle
3
zweifellos die beste Lösung! Übrigens, laut docs sollte es "string_escape" (mit Unterstrich) sein, aber aus irgendeinem Grund akzeptiert es alles im Muster "String Escape", "String @ Escape" und so weiter ... im Grunde'string\W+escape'
Nas Banov
2
@Nas Banov Die Dokumentation macht eine kleine Erwähnung darüber :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
29
Diese Lösung ist nicht gut genug, da sie nicht den Fall behandelt, in dem die ursprüngliche Zeichenfolge legitime Unicode-Zeichen enthält. Wenn Sie versuchen: >>> print("juancarlo\\tañez".encode('utf-8').decode('unicode_escape')) Sie erhalten: juancarlo añez
Apalala
2
Einverstanden mit @Apalala: Das ist nicht gut genug. Schauen Sie sich die Antwort von rseeper unten an, um eine vollständige Lösung zu finden, die in Python2 und 3 funktioniert!
Christian Aichinger
2
Da von latin1angenommen wird unicode_escape, wiederholen Sie das Codierungs- / Decodierungsbit, z. B.s.encode('utf-8').decode('unicode_escape').encode('latin1').decode('utf8')
Metatoaster
121

unicode_escape funktioniert im Allgemeinen nicht

Es stellt sich heraus, dass die Lösung string_escapeoder unicode_escapeim Allgemeinen nicht funktioniert - insbesondere nicht , wenn tatsächlich Unicode vorhanden ist.

Wenn Sie sicher sein können, dass jedes Nicht-ASCII-Zeichen maskiert wird (und denken Sie daran, dass alles, was über die ersten 128 Zeichen hinausgeht, kein ASCII-Zeichen ist), unicode_escapewird dies das Richtige für Sie tun. Wenn Ihre Zeichenfolge jedoch bereits wörtliche Nicht-ASCII-Zeichen enthält, wird ein Fehler auftreten.

unicode_escapeist grundsätzlich darauf ausgelegt, Bytes in Unicode-Text umzuwandeln. Aber an vielen Stellen - zum Beispiel im Python-Quellcode - sind die Quelldaten bereits Unicode-Text.

Dies kann nur dann richtig funktionieren, wenn Sie den Text zuerst in Bytes codieren. UTF-8 ist die sinnvolle Codierung für den gesamten Text, damit das funktioniert, oder?

Die folgenden Beispiele befinden sich in Python 3, sodass die Zeichenfolgenliterale sauberer sind. Das gleiche Problem besteht jedoch bei leicht unterschiedlichen Erscheinungsformen in Python 2 und 3.

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

Nun, das ist falsch.

Die neue empfohlene Methode zur Verwendung von Codecs, die Text in Text dekodieren, besteht darin, codecs.decodedirekt aufzurufen . Hilft das?

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

Überhaupt nicht. (Außerdem ist das Obige ein UnicodeError unter Python 2.)

Der unicode_escapeCodec geht trotz seines Namens davon aus, dass alle Nicht-ASCII-Bytes in der Latin-1-Codierung (ISO-8859-1) vorliegen. Sie müssten es also so machen:

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

Aber das ist schrecklich. Dies beschränkt Sie auf die 256 Latin-1-Zeichen, als wäre Unicode überhaupt nicht erfunden worden!

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

Hinzufügen eines regulären Ausdrucks zur Lösung des Problems

(Überraschenderweise haben wir jetzt keine zwei Probleme.)

Was wir tun müssen, ist, den unicode_escapeDecoder nur auf Dinge anzuwenden , von denen wir sicher sind, dass sie ASCII-Text sind. Insbesondere können wir sicherstellen, dass es nur auf gültige Python-Escape-Sequenzen angewendet wird, bei denen es sich garantiert um ASCII-Text handelt.

Der Plan ist, Escape-Sequenzen mit einem regulären Ausdruck zu finden und eine Funktion als Argument zu verwenden re.sub, um sie durch ihren nicht entkoppelten Wert zu ersetzen.

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)

Und damit:

>>> print(decode_escapes('Ernő \\t Rubik'))
Ernő     Rubik
rspeer
quelle
2
Wir brauchen umfassendere Arten solcher Antworten. Vielen Dank.
v.oddou
Funktioniert das os.sepüberhaupt? Ich versuche das: patt = '^' + self.prefix + os.sep ; name = sub(decode_escapes(patt), '', name)und es funktioniert nicht. Semikolon ist dort anstelle einer neuen Zeile.
Pureferret
@Pureferret Ich bin mir nicht sicher, was Sie fragen, aber Sie sollten dies wahrscheinlich nicht für Zeichenfolgen ausführen, bei denen der Backslash eine andere Bedeutung hat, z. B. Windows-Dateipfade. (Ist es das, was Sie os.sepsind?) Wenn Sie in Ihren Windows-Verzeichnisnamen umgekehrte Escape-Sequenzen verwendet haben, ist die Situation so gut wie nicht behebbar.
rspeer
Die Escape-Sequenz enthält keine Escape-
Zeichen
Das sagt mir, dass Sie einen anderen regulären Ausdruck mit einem Backslash beendet haben: stackoverflow.com/questions/4427174/…
rspeer
33

Die eigentlich richtige und bequeme Antwort für 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

Details zu codecs.escape_decode:

  • codecs.escape_decode ist ein Byte-zu-Byte-Decoder
  • codecs.escape_decodedecodiert ASCII-Escape-Sequenzen wie: b"\\n"-> b"\n", b"\\xce"-> b"\xce".
  • codecs.escape_decode kümmert sich nicht um die Codierung des Byte-Objekts oder muss diese kennen, aber die Codierung der maskierten Bytes sollte mit der Codierung des restlichen Objekts übereinstimmen.

Hintergrund:

  • @rspeer ist richtig: unicode_escapeist die falsche Lösung für Python3. Dies liegt daran, dass unicode_escapedekodierte Bytes decodiert und dann Bytes in Unicode-Zeichenfolgen decodiert werden, aber keine Informationen darüber erhalten, welcher Codec für die zweite Operation verwendet werden soll.
  • @ Jerub ist richtig: Vermeiden Sie die AST oder Auswertung.
  • Ich entdeckte zuerst codecs.escape_decodeaus dieser Antwort: "Wie kann ich .decode ('string-Escape') in Python3?" . Wie in dieser Antwort angegeben, ist diese Funktion derzeit für Python 3 nicht dokumentiert.
user19087
quelle
Dies ist die eigentliche Antwort (: Schade, dass sie auf einer schlecht dokumentierten Funktion beruht.
JWD
5
Dies ist die Antwort für Situationen, in denen die Escape-Sequenzen, die Sie haben, Escape- \xZeichen von UTF-8-Bytes sind. Da es jedoch Bytes in Bytes dekodiert, kann und kann es keine Escapezeichen von Nicht-ASCII-Unicode-Zeichen wie \uEscapezeichen dekodieren .
rspeer
Nur zu Ihrer Information, diese Funktion ist technisch nicht öffentlich. siehe bugs.python.org/issue30588
Hack5
8

Die ast.literal_evalFunktion kommt nahe, aber es wird erwartet, dass die Zeichenfolge zuerst richtig in Anführungszeichen gesetzt wird.

Natürlich hängt Pythons Interpretation von Backslash-Escapezeichen davon ab, wie die Zeichenfolge in Anführungszeichen gesetzt wird ( ""vs r""vs u"", dreifache Anführungszeichen usw.). Daher möchten Sie möglicherweise die Benutzereingaben in geeignete Anführungszeichen setzen und an übergeben literal_eval. Wenn Sie es in Anführungszeichen setzen, wird auch verhindert literal_eval, dass eine Zahl, ein Tupel, ein Wörterbuch usw. zurückgegeben werden.

Es kann immer noch schwierig werden, wenn der Benutzer nicht zitierte Anführungszeichen des Typs eingibt, den Sie um die Zeichenfolge wickeln möchten.

Greg Hewgill
quelle
Aha. Dies scheint potenziell gefährlich zu sein , wie Sie sagen: myString = "\"\ndoBadStuff()\n\"", print(ast.literal_eval('"' + myString + '"'))scheint zu laufen Code zu versuchen. Wie ist ast.literal_evaletwas anders / sicherer als eval?
dln385
5
@ dln385: literal_evalführt niemals Code aus. In der Dokumentation heißt es: "Dies kann zum sicheren Auswerten von Zeichenfolgen verwendet werden, die Python-Ausdrücke aus nicht vertrauenswürdigen Quellen enthalten, ohne dass die Werte selbst analysiert werden müssen."
Greg Hewgill
2

Dies ist eine schlechte Methode, aber es hat bei mir funktioniert, als ich versucht habe, maskierte Oktale zu interpretieren, die in einem String-Argument übergeben wurden.

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

Es ist erwähnenswert, dass es einen Unterschied zwischen eval und ast.literal_eval gibt (eval ist viel unsicherer). Siehe Verwenden von pythons eval () vs. ast.literal_eval ()?

LimeTr33
quelle
0

Der folgende Code sollte funktionieren für \ n muss in der Zeichenfolge angezeigt werden.

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
quelle
1
Dies funktioniert nicht wie geschrieben (die Schrägstriche machen replacenichts), verwendet stark veraltete APIs (die stringModulfunktionen dieser Art sind ab Python 2.0 veraltet, werden durch die strMethoden ersetzt und sind in Python 3 vollständig verschwunden) und nur behandelt den speziellen Fall des Ersetzens einer einzelnen neuen Zeile, nicht die allgemeine Escape-Verarbeitung.
ShadowRanger