支持字符串和字节序列的双模式API
正则表达式中的字符串和字节序列
如果使用字节序列构建正则表达式,\d和\w等模式只能匹配ASCII字符;相比之下,如果是字符串模式,就能匹配ASCII之外的Unicode数字或字母。
比较简单的字符串正则表达式和字节序列正则表达式的行为
import re
re_numbers_str = re.compile(r'\d+') #➊
re_words_str = re.compile(r'\w+')
re_numbers_bytes = re.compile(rb'\d+') #➋
re_words_bytes = re.compile(rb'\w+')
text_str = ("Ramanujan saw \u0be7\u0bed\u0be8\u0bef" #➌
" as 1729 = 1³ + 12³ = 9³ + 10³.") #➍
text_bytes = text_str.encode('utf_8') #➎
print('Text', repr(text_str), sep='\n ')
print('Numbers')
print(' str :', re_numbers_str.findall(text_str)) #➏
print(' bytes:', re_numbers_bytes.findall(text_bytes)) #➐
print('Words')
print(' str :', re_words_str.findall(text_str)) #➑
print(' bytes:', re_words_bytes.findall(text_bytes)) #➒
➊ 前两个正则表达式是字符串类型。
➋ 后两个正则表达式是字节序列类型。
➌ 要搜索的Unicode文本,包括1729的泰米尔数字(逻辑行直到右括号才结束)。
➍ 这个字符串在编译时与前一个拼接起来
➎ 字节序列只能用字节序列正则表达式搜索。
➏ 字符串模式r’\d+‘能匹配泰米尔数字和ASCII数字。
➐ 字节序列模式rb’\d+‘只能匹配ASCII字节中的数字。
➑ 字符串模式r’\w+‘能匹配字母、上标、泰米尔数字和ASCII数字。
➒ 字节序列模式rb’\w+'只能匹配ASCII字节中的字母和数字。
输出
Text
'Ramanujan saw ௧௭௨௯ as 1729 = 1³ + 12³ = 9³ + 10³.'
Numbers
str : ['௧௭௨௯', '1729', '1', '12', '9', '10']
bytes: [b'1729', b'1', b'12', b'9', b'10']
Words
str : ['Ramanujan', 'saw', '௧௭௨௯', 'as', '1729', '1³', '12³', '9³', '10³']
bytes: [b'Ramanujan', b'saw', b'as', b'1729', b'1', b'12', b'9', b'10']
分析:可以使用正则表达式搜索字符串和字节序列,但是在后一种情况中,ASCII范围外的字节不会当成数字和组成单词的字母。
字符串正则表达式有个re.ASCII标志,它让\w、\W、\b、\B、\d、\D、\s和\S只匹配ASCII字符。
os函数中的字符串和字节序列
os模块中的所有函数、文件名或路径名参数既能使用字符串,也能使用字节序列。
如果这样的函数使用字符串参数调用,该参数会使用sys.getfilesystemencoding()得到的编解码器自动编码,然后操作系统会使用相同的编解码器解码。这几乎就是我们想要的行为,与Unicode三明治最佳实践一致。
#把字符串和字节序列参数传给listdir函数得到的结果
>>> os.listdir('.') # ➊
['abc.txt', 'digits-of-π.txt']
>>> os.listdir(b'.') # ➋
[b'abc.txt', b'digits-of-\xcf\x80.txt']
➊ 第二个文件名是“digits-of-π.txt”(有一个希腊字母π)。
➋ 参数是字节序列,listdir函数返回的文件名也是字节序列:b’\xcf\x80’是希腊字母π的UTF-8编码。
为了便于手动处理字符串或字节序列形式的文件名或路径名,os模块提供了特殊的编码和解码函数。
- fsencode(filename)
如果filename是str类型(此外还可能是bytes类型),使用sys.getfilesystemencoding()返回的编解码器把filename编码成字节序列;否则,返回未经修改的filename字节 序列。
- fsdecode(filename)
如果filename是bytes类型(此外还可能是str类型),使用sys.getfilesystemen- coding()返回的编解码器把filename解码成字符串;否则,返回未经修改的filename字符串。
使用surrogateescape处理鬼符
Python 3.1引入的surrogateescape编解码器错误处理方式是处理意外字节序列或未知编码的一种方式。
这种错误处理方式会把每个无法解码的字节替换成Unicode中U+DC00到U+DCFF之间的码位。
#使用surrogateescape错误处理方式
>>> os.listdir('.') #➊
['abc.txt', 'digits-of-π.txt']
>>> os.listdir(b'.') #➋
[b'abc.txt', b'digits-of-\xcf\x80.txt']
>>> pi_name_bytes = os.listdir(b'.')[1] #➌
>>> pi_name_str = pi_name_bytes.decode('ascii', 'surrogateescape') #➍
>>> pi_name_str #➎
'digits-of-\udccf\udc80.txt'
>>> pi_name_str.encode('ascii', 'surrogateescape') #➏
b'digits-of-\xcf\x80.txt'
➊ 列出目录里的文件,有个文件名中包含非ASCII字符。
➋ 假设我们不知道编码,获取文件名的字节序列形式。
➌ pi_names_bytes是包含π的文件名。
➍ 使用’ascii’编解码器和’surrogateescape’错误处理方式把它解码成字符串。
➎ 各个非ASCII字节替换成代替码位:’\xcf\x80’变成了’\udccf\udc80’。
➏ 编码成ASCII字节序列:各个代替码位还原成被替换的字节。