在Python中处理字符串中的转义序列
有时候,当我从文件或者用户那里获取输入时,得到的字符串里面会包含一些转义序列。我想要以和Python处理字符串中的转义序列一样的方式来处理这些转义序列。
比如说,假设myString
是这样定义的:
>>> myString = "spam\\neggs"
>>> print(myString)
spam\neggs
我想要一个函数(我叫它process
),它能做到这一点:
>>> print(process(myString))
spam
eggs
这个函数很重要,因为它需要能够处理Python中所有的转义序列(在上面的链接中有一个表格列出了这些转义序列)。
Python有没有这样的函数呢?
8 个回答
对于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
不需要知道字节对象的编码方式,但被转义的字节的编码应该和对象的其他部分的编码一致。
背景信息:
- @rspeer 说得对:
unicode_escape
在Python 3中是错误的解决方案。因为unicode_escape
先解码被转义的字节,然后再把字节解码成Unicode字符串,但它不知道第二步该用哪个编码。 - @Jerub 也说得对:应该避免使用AST或eval。
- 我第一次发现
codecs.escape_decode
是通过这个回答,关于“如何在Python 3中使用 .decode('string-escape')?”。正如那个回答所说,这个函数在Python 3中目前没有文档说明。
unicode_escape
并不总是有效
事实证明,string_escape
或 unicode_escape
的方法并不总是有效,特别是在有实际的Unicode字符存在时,它们就会出问题。
如果你能确保每一个非ASCII字符都会被转义(记住,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
的名字里有“unicode”,但它实际上假设所有非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)
添加正则表达式来解决问题
(令人惊讶的是,我们现在有了两个问题。)
我们需要做的是只对我们确定是ASCII文本的内容应用 unicode_escape
解码器。特别是,我们可以确保只对有效的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
正确的做法是使用 'string-escape' 代码来解码字符串。
>>> 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。这些字符串编码的方法要安全得多。