Proses urutan pelolosan dalam string dengan Python

112

Terkadang ketika saya mendapatkan input dari file atau pengguna, saya mendapatkan string dengan urutan escape di dalamnya. Saya ingin memproses urutan escape dengan cara yang sama seperti proses Python melarikan diri urutan dalam string literal .

Misalnya, myStringdidefinisikan sebagai:

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

Saya menginginkan fungsi (saya akan menyebutnya process) yang melakukan ini:

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

Penting agar fungsi tersebut dapat memproses semua urutan escape dengan Python (tercantum dalam tabel di tautan di atas).

Apakah Python memiliki fungsi untuk melakukan ini?

dln385.dll
sumber
1
hmmm, bagaimana tepatnya Anda mengharapkan string berisi 'spam'+"eggs"+'''some'''+"""more"""diproses?
Nas Banov
@Nas Banov Itu ujian yang bagus. String tersebut tidak berisi urutan escape, jadi harus sama persis setelah diproses. myString = "'spam'+\"eggs\"+'''some'''+\"\"\"more\"\"\"", print(bytes(myString, "utf-8").decode("unicode_escape"))sepertinya berhasil.
dln385
5
Sebagian besar jawaban atas pertanyaan ini memiliki masalah yang serius. Sepertinya tidak ada cara standar untuk menghormati urutan escape dengan Python tanpa merusak unicode. Jawaban yang diposting oleh @rspeer adalah yang saya adopsi untuk Grako karena sejauh ini menangani semua kasus yang diketahui.
Apalala

Jawaban:

138

Hal yang benar untuk dilakukan adalah menggunakan kode 'string-escape' untuk memecahkan kode 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

Jangan gunakan AST atau eval. Menggunakan codec string jauh lebih aman.

Jerub
sumber
3
tangan ke bawah, solusi terbaik ! btw, menurut dokumen itu harus "string_escape" (dengan garis bawah) tetapi untuk beberapa alasan menerima apa pun dalam pola 'string escape', 'string @ escape "dan yang lainnya ... pada dasarnya'string\W+escape'
Nas Banov
2
@Nas Banov Dokumentasi tidak menyebutkan hal itu :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
Solusi ini tidak cukup baik karena tidak menangani kasus di mana terdapat karakter unicode yang sah dalam string aslinya. Jika Anda mencoba: >>> print("juancarlo\\tañez".encode('utf-8').decode('unicode_escape')) Anda mendapatkan: juancarlo añez
Apalala
2
Setuju dengan @Apalala: ini tidak cukup bagus. Lihat jawaban rseeper di bawah ini untuk solusi lengkap yang bekerja dengan Python2 dan 3!
Christian Aichinger
2
Karena latin1diasumsikan oleh unicode_escape, ulangi bit encode / decode, misalnyas.encode('utf-8').decode('unicode_escape').encode('latin1').decode('utf8')
metatoaster
121

unicode_escape tidak berfungsi secara umum

Ternyata solusi string_escapeatau unicode_escapetidak berfungsi secara umum - terutama, tidak berfungsi dengan adanya Unicode yang sebenarnya.

Jika Anda dapat yakin bahwa setiap karakter non-ASCII akan di-escape (dan ingat, apa pun di luar 128 karakter pertama adalah non-ASCII), unicode_escapeakan melakukan hal yang benar untuk Anda. Tetapi jika sudah ada karakter non-ASCII literal dalam string Anda, semuanya akan salah.

unicode_escapepada dasarnya dirancang untuk mengubah byte menjadi teks Unicode. Tetapi di banyak tempat - misalnya, kode sumber Python - data sumber sudah berupa teks Unicode.

Satu-satunya cara ini dapat bekerja dengan benar adalah jika Anda mengenkode teks menjadi byte terlebih dahulu. UTF-8 adalah pengkodean yang masuk akal untuk semua teks, sehingga seharusnya berfungsi, bukan?

Contoh berikut ada di Python 3, sehingga literal string lebih bersih, tetapi masalah yang sama ada dengan manifestasi yang sedikit berbeda pada Python 2 dan 3.

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

Itu salah.

Cara baru yang disarankan untuk menggunakan codec yang mendekode teks menjadi teks adalah dengan memanggil codecs.decodesecara langsung. Apakah itu membantu?

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

Tidak semuanya. (Juga, di atas adalah UnicodeError pada Python 2.)

The unicode_escapecodec, meskipun namanya, ternyata menganggap bahwa semua byte non-ASCII berada di Latin-1 (ISO-8859-1) encoding. Jadi, Anda harus melakukannya seperti ini:

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

Tapi itu mengerikan. Ini membatasi Anda pada 256 karakter Latin-1, seolah Unicode belum pernah ditemukan sama sekali!

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

Menambahkan ekspresi reguler untuk menyelesaikan masalah

(Anehnya, sekarang kami tidak memiliki dua masalah.)

Yang perlu kita lakukan hanyalah menerapkan unicode_escapedecoder ke hal-hal yang kita yakini teks ASCII. Secara khusus, kami dapat memastikan hanya untuk menerapkannya ke urutan escape Python yang valid, yang dijamin menjadi teks ASCII.

Rencananya, kita akan menemukan escape sequence menggunakan regular expression, dan menggunakan fungsi sebagai argumen re.subuntuk menggantinya dengan nilai unescaped.

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)

Dan dengan itu:

>>> print(decode_escapes('Ernő \\t Rubik'))
Ernő     Rubik
rspeer
sumber
2
kita membutuhkan jenis jawaban yang lebih luas seperti itu. Terima kasih.
v.oddou
Apakah ini bekerja os.sepsama sekali? Saya mencoba melakukan ini: patt = '^' + self.prefix + os.sep ; name = sub(decode_escapes(patt), '', name)dan tidak berhasil. Titik koma ada di sana sebagai pengganti baris baru.
Pureferret
@ Pureferret Saya tidak begitu yakin apa yang Anda tanyakan, tetapi Anda mungkin tidak boleh menjalankan ini pada string di mana garis miring terbalik memiliki arti yang berbeda, seperti jalur file Windows. (Itukah milik Anda os.sep?) Jika Anda memiliki urutan escape dengan garis miring terbalik di nama direktori Windows Anda, situasinya tidak dapat dipulihkan.
rspeer
Urutan pelarian tidak memiliki pelolosan di dalamnya, tetapi saya mendapatkan kesalahan 'string pelarian palsu'
Pureferret
Itu memberi tahu saya bahwa Anda mengakhiri beberapa ekspresi reguler lainnya dengan garis miring terbalik: stackoverflow.com/questions/4427174/…
rspeer
33

Jawaban yang benar dan nyaman untuk 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

Rincian tentang codecs.escape_decode:

  • codecs.escape_decode adalah decoder byte-ke-byte
  • codecs.escape_decodemendekode urutan escape ascii, seperti: b"\\n"-> b"\n", b"\\xce"-> b"\xce".
  • codecs.escape_decode tidak peduli atau perlu mengetahui tentang pengkodean objek byte, tetapi pengkodean byte yang lolos harus cocok dengan pengkodean sisa objek.

Latar Belakang:

  • @rspeer benar: unicode_escapeadalah solusi yang salah untuk python3. Ini karena unicode_escapemendekode byte yang lolos, kemudian mendekode byte ke string unicode, tetapi tidak menerima informasi mengenai codec mana yang akan digunakan untuk operasi kedua.
  • @ Jerub benar: hindari AST atau eval.
  • Saya pertama kali menemukan codecs.escape_decodedari jawaban ini untuk "bagaimana saya .decode ('string-escape') di Python3?" . Seperti yang dinyatakan oleh jawaban tersebut, fungsi tersebut saat ini tidak didokumentasikan untuk python 3.
pengguna19087
sumber
Ini adalah jawaban yang sebenarnya (: Sayang sekali itu bergantung pada fungsi yang didokumentasikan dengan buruk.
jwd
5
Ini adalah jawaban untuk situasi di mana urutan escape yang Anda miliki adalah escape \xdari byte UTF-8. Tetapi karena ia mendekode byte menjadi byte, ia tidak - dan tidak dapat - mendekode pelarian apa pun dari karakter Unicode non-ASCII, seperti \upelarian.
rspeer
Sekadar info, fungsi ini secara teknis tidak bersifat publik. lihat bugs.python.org/issue30588
Hack5
8

The ast.literal_evalFungsi datang dekat, tetapi akan mengharapkan string akan benar dikutip pertama.

Tentu saja interpretasi Python untuk pelolosan garis miring terbalik bergantung pada bagaimana string dikutip ( ""vs r""vs u"", tanda kutip tiga, dll) sehingga Anda mungkin ingin memasukkan masukan pengguna dalam tanda kutip yang sesuai dan meneruskan ke literal_eval. Membungkusnya dalam tanda kutip juga akan mencegah literal_evalpengembalian angka, tupel, kamus, dll.

Hal-hal mungkin masih rumit jika pengguna mengetikkan tanda kutip dari jenis yang ingin Anda bungkus di sekitar string.

Greg Hewgill
sumber
Saya melihat. Hal ini tampaknya menjadi berpotensi berbahaya seperti yang Anda katakan: myString = "\"\ndoBadStuff()\n\"", print(ast.literal_eval('"' + myString + '"'))tampaknya mencoba untuk menjalankan kode. Bagaimana bisa ast.literal_evalberbeda / lebih aman dari eval?
dln385
5
@ dln385: literal_evaltidak pernah menjalankan kode. Dari dokumentasi, "Ini dapat digunakan untuk mengevaluasi string yang berisi ekspresi Python dari sumber yang tidak tepercaya tanpa perlu mengurai nilainya sendiri."
Greg Hewgill
2

Ini adalah cara yang buruk untuk melakukannya, tetapi berhasil bagi saya ketika mencoba menafsirkan oktal yang lolos yang diteruskan dalam argumen string.

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

Perlu disebutkan bahwa ada perbedaan antara eval dan ast.literal_eval (eval jauh lebih tidak aman). Lihat Menggunakan python's eval () vs. ast.literal_eval ()?

LimeTr33
sumber
0

Kode di bawah ini harus berfungsi karena \ n diperlukan untuk ditampilkan pada 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
sumber
1
Ini tidak berfungsi seperti yang tertulis (garis miring ke depan membuat replacetidak melakukan apa-apa), menggunakan API yang sangat usang ( stringfungsi modul semacam ini tidak digunakan lagi pada Python 2.0, diganti dengan strmetode, dan hilang sepenuhnya dengan Python 3), dan hanya menangani kasus khusus untuk mengganti satu baris baru, bukan pemrosesan pelolosan umum.
ShadowRanger