[英]Process escape sequences in a string in Python
有時,當我從文件或用戶獲得輸入時,我會得到一個帶有轉義序列的字符串。 我想以與 Python 處理字符串文字中的轉義序列相同的方式處理轉義序列。
例如,假設myString
定義為:
>>> myString = "spam\\neggs"
>>> print(myString)
spam\neggs
我想要一個 function(我稱之為process
)來執行此操作:
>>> print(process(myString))
spam
eggs
重要的是 function 可以處理 Python 中的所有轉義序列(在上面鏈接的表格中列出)。
Python 是否有 function 來執行此操作?
正確的做法是使用“字符串轉義”代碼來解碼字符串。
>>> 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
不要使用 AST 或 eval。 使用字符串編解碼器要安全得多。
unicode_escape
通常不起作用事實證明, string_escape
或unicode_escape
解決方案通常不起作用——特別是,它在實際 Unicode 存在的情況下不起作用。
如果您可以確定每個非 ASCII 字符都會被轉義(請記住,前 128 個字符之外的任何字符都是非 ASCII), unicode_escape
將為您做正確的事情。 但是如果你的字符串中已經有任何文字的非 ASCII 字符,事情就會出錯。
unicode_escape
從根本上設計用於將字節轉換為 Unicode 文本。 但在許多地方——例如 Python 源代碼——源數據已經是 Unicode 文本。
唯一可以正常工作的方法是先將文本編碼為字節。 UTF-8 是所有文本的合理編碼,所以應該可以,對吧?
以下示例在 Python 3 中,因此字符串文字更清晰,但同樣的問題存在於 Python 2 和 3 中的表現形式略有不同。
>>> s = 'naïve \\t test'
>>> print(s.encode('utf-8').decode('unicode_escape'))
naïve test
嗯,這是錯誤的。
使用將文本解碼為文本的編解碼器的新推薦方法是直接調用codecs.decode
。 這有幫助嗎?
>>> import codecs
>>> print(codecs.decode(s, 'unicode_escape'))
naïve test
一點也不。 (另外,上面是 Python 2 上的 UnicodeError。)
unicode_escape
編解碼器,盡管它的名字,結果是假設所有非 ASCII 字節都在 Latin-1 (ISO-8859-1) 編碼中。 所以你必須這樣做:
>>> print(s.encode('latin-1').decode('unicode_escape'))
naïve test
但這太可怕了。 這將您限制為 256 個 Latin-1 字符,就好像 Unicode 根本就沒有被發明過一樣!
>>> 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)
(令人驚訝的是,我們現在沒有兩個問題。)
我們需要做的只是將unicode_escape
解碼器應用於我們確定為 ASCII 文本的內容。 特別是,我們可以確保僅將其應用於有效的 Python 轉義序列,這些轉義序列保證為 ASCII 文本。
計划是,我們將使用正則表達式找到轉義序列,並使用函數作為re.sub
的參數,以將它們替換為未轉義的值。
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)
有了這個:
>>> print(decode_escapes('Ernő \\t Rubik'))
Ernő Rubik
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
關於codecs.escape_decode
的詳細信息:
codecs.escape_decode
是一個字節到字節的解碼器codecs.escape_decode
解碼 ascii 轉義序列,例如: b"\\\\n"
-> b"\\n"
, b"\\\\xce"
-> b"\\xce"
。codecs.escape_decode
不關心或不需要知道字節對象的編碼,但轉義字節的編碼應該與對象其余部分的編碼相匹配。背景:
unicode_escape
是 python3 的錯誤解決方案。 這是因為unicode_escape
解碼轉義字節,然后將字節解碼為 unicode 字符串,但沒有接收到有關用於第二個操作的編解碼器的信息。codecs.escape_decode
“我如何在 Python3 中進行 .decode('string-escape')?” . 正如該答案所述,該函數目前沒有為 python 3 記錄。 ast.literal_eval
函數很接近,但它希望字符串首先被正確引用。
當然,Python 對反斜杠轉義的解釋取決於字符串的引用方式( ""
vs r""
vs u""
,三引號等),因此您可能希望將用戶輸入包裝在合適的引號中並傳遞給literal_eval
。 用引號括起來也可以防止literal_eval
返回數字、元組、字典等。
如果用戶鍵入您打算環繞字符串的類型的不帶引號的引號,事情仍然可能會變得棘手。
這是一種不好的方法,但是在嘗試解釋在字符串參數中傳遞的轉義八進制時它對我有用。
input_string = eval('b"' + sys.argv[1] + '"')
值得一提的是 eval 和 ast.literal_eval 之間存在差異(eval 更加不安全)。 請參閱使用 python 的 eval() 與 ast.literal_eval()?
Jerub(當前)接受的答案對於 python2 是正確的,但對於 python3 是不正確的並且可能會產生亂碼結果(正如 Apalala 在對該解決方案的評論中指出的那樣)。 這是因為根據官方python 文檔,unicode_escape 編解碼器要求其源代碼以 latin-1 而非 utf-8 編碼。 因此,在 python3 中使用:
>>> myString="špåm\\nëðþ\\x73"
>>> print(myString)
špåm\nëðþ\x73
>>> decoded_string = myString.encode('latin-1','backslashreplace').decode('unicode_escape')
>>> print(decoded_string)
špåm
ëðþs
此方法還避免了 metatoaster 對 Jerub 解決方案的注釋中字符串和字節之間額外不必要的往返(但要感謝 metatoaster 以識別該解決方案中的錯誤)。
正確引用字符串,使其看起來像等效的 Python 字符串文字,然后使用ast.literal_eval
。 這是安全的,但比您預期的要正確得多。
在字符串的開頭和結尾添加一個"
很容易,但我們還需要確保字符串中的任何"
都被正確轉義。 如果我們想要完全符合 Python 的翻譯,我們需要考慮無效轉義序列的棄用行為。
結果是我們需要添加一個反斜杠:
偶數個反斜杠后跟雙引號的任意序列(以便我們在需要時轉義引號,但不要轉義反斜杠,如果引號已經轉義則取消轉義引號); 也
輸入末尾的一系列奇數反斜杠(因為否則反斜杠會逃脫我們封閉的雙引號)。
這是一個酸性測試輸入,顯示了一堆困難的情況:
>>> text = r'''\\ \ \" \\" \\\" \'你好'\n\u062a\xff\N{LATIN SMALL LETTER A}"''' + '\\'
>>> text
'\\\\ \\ \\" \\\\" \\\\\\" \\\'你好\'\\n\\u062a\\xff\\N{LATIN SMALL LETTER A}"\\'
>>> print(text)
\\ \ \" \\" \\\" \'你好'\n\u062a\xff\N{LATIN SMALL LETTER A}"\
我最終能夠計算出一個正則表達式來正確處理所有這些情況,允許使用literal_eval
:
>>> def parse_escapes(text):
... fixed_escapes = re.sub(r'(?<!\\)(\\\\)*("|\\$)', r'\\\1\2', text)
... return ast.literal_eval(f'"{fixed_escapes}"')
...
測試結果:
>>> parse_escapes(text)
'\\ \\ " \\" \\" \'你好\'\nتÿa"\\'
>>> print(parse_escapes(text))
\ \ " \" \" '你好'
تÿa"\
這應該可以正確處理所有內容——包含單引號和雙引號的字符串、每個帶有反斜杠的奇怪情況以及輸入中的非 ASCII 字符。 (我承認用肉眼驗證結果有點困難!)
下面的代碼應該適用於 \\n 需要顯示在字符串上。
import string
our_str = 'The String is \\n, \\n and \\n!'
new_str = string.replace(our_str, '/\\n', '/\n', 1)
print(new_str)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.