Xử lý chuỗi thoát trong một chuỗi bằng Python


112

Đôi khi khi tôi nhận đầu vào từ một tệp hoặc từ người dùng, tôi nhận được một chuỗi có các chuỗi thoát trong đó. Tôi muốn xử lý các chuỗi thoát theo cách giống như cách Python xử lý các chuỗi thoát trong chuỗi ký tự .

Ví dụ, giả sử myStringđược định nghĩa là:

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

Tôi muốn một hàm (tôi sẽ gọi nó process) thực hiện điều này:

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

Điều quan trọng là hàm có thể xử lý tất cả các chuỗi thoát trong Python (được liệt kê trong bảng trong liên kết ở trên).

Python có chức năng để làm điều này không?


1
hmmm, chính xác thì bạn mong đợi một chuỗi chứa 'spam'+"eggs"+'''some'''+"""more"""được xử lý như thế nào?
Nas Banov

@Nas Banov Đó là một bài kiểm tra tốt. Chuỗi đó không chứa trình tự thoát, do đó, nó phải giống hệt nhau sau khi xử lý. myString = "'spam'+\"eggs\"+'''some'''+\"\"\"more\"\"\"", print(bytes(myString, "utf-8").decode("unicode_escape"))có vẻ hoạt động.
dln385

5
Hầu hết các câu trả lời cho câu hỏi này đều có vấn đề nghiêm trọng. Dường như không có cách tiêu chuẩn nào để tôn vinh các chuỗi thoát trong Python mà không phá vỡ unicode. Câu trả lời được đăng bởi @rspeer là câu trả lời mà tôi đã áp dụng cho Grako vì nó cho đến nay xử lý tất cả các trường hợp đã biết.
Apalala

Câu trả lời:


138

Điều chính xác cần làm là sử dụng mã 'string-Escape' để giải mã chuỗi.

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

Không sử dụng AST hoặc eval. Sử dụng codec chuỗi an toàn hơn nhiều.


3
xuống tay, giải pháp tốt nhất ! btw, bởi tài liệu nó cần được "string_escape" (với dấu gạch dưới) nhưng đối với một số lý do chấp nhận bất cứ điều gì trong mô hình 'chuỗi escape', 'string @ thoát" và không có điều gì ... về cơ bản'string\W+escape'
Nas Banov

2
@Nas Banov Các tài liệu không làm cho một đề cập đến nhỏ về điều đó :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
Giải pháp này không đủ tốt vì nó không xử lý được trường hợp có các ký tự unicode hợp pháp trong chuỗi gốc. Nếu bạn thử: >>> print("juancarlo\\tañez".encode('utf-8').decode('unicode_escape')) Bạn nhận được: juancarlo añez
Apalala

2
Đồng ý với @Apalala: điều này vẫn chưa đủ tốt. Hãy xem câu trả lời của rseeper bên dưới để có giải pháp hoàn chỉnh hoạt động trong Python2 và 3!
Christian Aichinger

2
latin1được giả định bởi unicode_escape, hãy làm lại bit mã hóa / giải mã, ví dụs.encode('utf-8').decode('unicode_escape').encode('latin1').decode('utf8')
metatoaster

121

unicode_escape nói chung không hoạt động

Nó chỉ ra rằng giải pháp string_escapehoặc unicode_escapegiải pháp không hoạt động nói chung - đặc biệt, nó không hoạt động khi có Unicode thực tế.

Nếu bạn có thể chắc chắn rằng mọi ký tự không phải ASCII sẽ được thoát (và hãy nhớ rằng bất kỳ thứ gì ngoài 128 ký tự đầu tiên đều không phải ASCII), unicode_escapesẽ thực hiện điều phù hợp với bạn. Nhưng nếu có bất kỳ ký tự không phải ASCII theo nghĩa đen nào đã có trong chuỗi của bạn, mọi thứ sẽ trở nên sai lầm.

unicode_escapevề cơ bản được thiết kế để chuyển đổi byte thành văn bản Unicode. Nhưng ở nhiều nơi - ví dụ, mã nguồn Python - dữ liệu nguồn đã là văn bản Unicode.

Cách duy nhất điều này có thể hoạt động chính xác là nếu bạn mã hóa văn bản thành byte trước. UTF-8 là mã hóa hợp lý cho tất cả văn bản, vì vậy nó sẽ hoạt động, phải không?

Các ví dụ sau đây là trong Python 3, để các ký tự chuỗi rõ ràng hơn, nhưng cùng một vấn đề tồn tại với các biểu hiện hơi khác nhau trên cả Python 2 và 3.

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

Chà, sai rồi.

Cách mới được đề xuất để sử dụng codec giải mã văn bản thành văn bản là gọi codecs.decodetrực tiếp. cái đó có giúp ích không?

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

Không có gì. (Ngoài ra, ở trên là một UnicodeError trên Python 2.)

Các unicode_escapecodec, mặc dù tên của nó, hóa ra giả định rằng tất cả các byte phi ASCII đang trong Latin-1 (ISO-8859-1) mã hóa. Vì vậy, bạn sẽ phải làm như thế này:

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

Nhưng điều đó thật kinh khủng. Điều này giới hạn bạn trong 256 ký tự Latinh-1, như thể Unicode chưa bao giờ được phát minh!

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

Thêm một biểu thức chính quy để giải quyết vấn đề

(Đáng ngạc nhiên là bây giờ chúng ta không có hai vấn đề.)

Những gì chúng ta cần làm là chỉ áp dụng unicode_escapebộ giải mã cho những thứ mà chúng ta chắc chắn là văn bản ASCII. Đặc biệt, chúng tôi có thể đảm bảo chỉ áp dụng nó cho các chuỗi thoát Python hợp lệ, được đảm bảo là văn bản ASCII.

Kế hoạch là, chúng ta sẽ tìm các chuỗi thoát bằng cách sử dụng một biểu thức chính quy và sử dụng một hàm làm đối số re.subđể thay thế chúng bằng giá trị không thoát của chúng.

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)

Và với điều đó:

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

2
chúng ta cần nhiều loại câu trả lời bao trùm hơn như vậy. cảm ơn.
v.oddou

Điều này có làm việc với os.septất cả? Tôi đang cố gắng làm điều này: patt = '^' + self.prefix + os.sep ; name = sub(decode_escapes(patt), '', name)và nó không hoạt động. Dấu chấm phẩy ở đó thay cho một dòng mới.
Pureferret

@ Pureferret Tôi thực sự không chắc bạn đang hỏi gì, nhưng có lẽ bạn không nên chạy điều này trên các chuỗi có dấu gạch chéo ngược có nghĩa khác, chẳng hạn như đường dẫn tệp Windows. (Đó có phải là của bạn os.sepkhông?) Nếu bạn có các trình tự thoát gạch chéo ngược trong tên thư mục Windows của mình, thì tình huống là không thể khôi phục được.
rspeer

Các dãy thoát không có thoát trong họ, nhưng tôi nhận được một 'thoát chuỗi giả' lỗi
Pureferret

Điều đó cho tôi biết rằng bạn đã kết thúc một số biểu thức chính quy khác bằng dấu gạch chéo ngược: stackoverflow.com/questions/4427174/…
rspeer

33

Câu trả lời thực sự chính xác và thuận tiện cho 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

Thông tin chi tiết về codecs.escape_decode:

  • codecs.escape_decode là một bộ giải mã byte-to-byte
  • codecs.escape_decodegiải mã chuỗi thoát ascii, chẳng hạn như: b"\\n"-> b"\n", b"\\xce"-> b"\xce".
  • codecs.escape_decode không quan tâm hoặc không cần biết về mã hóa của đối tượng byte, nhưng mã hóa của các byte thoát phải khớp với mã hóa của phần còn lại của đối tượng.

Lý lịch:

  • @rspeer là đúng: unicode_escapelà giải pháp không chính xác cho python3. Điều này là do unicode_escapegiải mã các byte thoát, sau đó giải mã các byte thành chuỗi unicode, nhưng không nhận được thông tin về codec nào sẽ sử dụng cho thao tác thứ hai.
  • @Jerub là đúng: tránh AST hoặc eval.
  • Lần đầu tiên tôi phát hiện ra codecs.escape_decodetừ câu trả lời này cho "làm cách nào để sử dụng .decode ('string-Escape') trong Python3?" . Như câu trả lời đó nêu rõ, chức năng đó hiện không được ghi lại cho python 3.

Đây là câu trả lời thực sự (: Thật tệ là nó dựa trên một chức năng được ghi chép kém.
jwd 21/02

5
Đây là câu trả lời cho các tình huống mà chuỗi thoát mà bạn có là \xthoát UTF-8 byte. Nhưng bởi vì nó giải mã từng byte thành từng byte, nó không - và không thể - giải mã bất kỳ lỗi nào của các ký tự Unicode không phải ASCII, chẳng hạn như \uthoát.
rspeer

Chỉ là FYI, chức năng này về mặt kỹ thuật không công khai. xem bug.python.org/issue30588
Hack5

8

Các ast.literal_evalchức năng đến gần, nhưng nó sẽ mong đợi các chuỗi được trích dẫn đúng đầu tiên.

Tất nhiên, việc giải thích dấu gạch chéo ngược của Python phụ thuộc vào cách chuỗi được trích dẫn ( ""vs r""vs u"", dấu ngoặc kép, v.v.), vì vậy bạn có thể muốn đưa đầu vào của người dùng trong dấu ngoặc kép phù hợp và chuyển đến literal_eval. Đặt nó trong dấu ngoặc kép cũng sẽ ngăn không cho literal_evaltrả về một số, bộ giá trị, từ điển, v.v.

Mọi thứ vẫn có thể trở nên phức tạp nếu người dùng nhập các dấu ngoặc kép chưa được trích dẫn của loại mà bạn định quấn quanh chuỗi.


Tôi hiểu rồi. Điều này dường như tiềm ẩn nguy hiểm như bạn nói : myString = "\"\ndoBadStuff()\n\"", print(ast.literal_eval('"' + myString + '"'))dường như đang cố gắng chạy mã. Làm thế nào là ast.literal_evalbất kỳ khác nhau / an toàn hơn eval?
dln385

5
@ dln385: literal_evalkhông bao giờ thực thi mã. Từ tài liệu, "Điều này có thể được sử dụng để đánh giá một cách an toàn các chuỗi chứa biểu thức Python từ các nguồn không đáng tin cậy mà không cần phải tự phân tích cú pháp các giá trị."
Greg Hewgill

2

Đây là một cách làm không tốt, nhưng nó đã hiệu quả với tôi khi cố gắng diễn giải các số bát phân thoát được truyền trong một đối số chuỗi.

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

Điều đáng nói là có sự khác biệt giữa eval và ast.literal_eval (eval không an toàn hơn). Xem Sử dụng eval () của python so với ast.literal_eval ()?


0

Mã bên dưới sẽ hoạt động cho \ n được yêu cầu hiển thị trên chuỗi.

import string

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

1
Điều này không hoạt động như đã viết (các dấu gạch chéo về phía trước replacekhông làm gì cả), sử dụng các API cực kỳ lỗi thời (các stringhàm mô-đun loại này không còn được dùng như Python 2.0, được thay thế bằng các strphương thức và biến mất hoàn toàn trong Python 3) và chỉ xử lý trường hợp cụ thể thay thế một dòng mới, không xử lý thoát chung chung.
ShadowRanger 19/02/19
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.