返回介绍

4.9 支持字符串和字节序列的双模式 API

发布于 2024-02-05 21:59:48 字数 4657 浏览 0 评论 0 收藏 0

标准库中的一些函数能接受字符串或字节序列为参数,然后根据类型展现不同的行为。re 和 os 模块中就有这样的函数。

4.9.1 正则表达式中的字符串和字节序列

如果使用字节序列构建正则表达式,\d 和 \w 等模式只能匹配 ASCII 字符;相比之下,如果是字符串模式,就能匹配 ASCII 之外的 Unicode 数字或字母。示例 4-22 和图 4-4 展示了字符串模式和字节序列模式中字母、ASCII 数字、上标和泰米尔数字的匹配情况。

示例 4-22 ramanujan.py:比较简单的字符串正则表达式和字节序列正则表达式的行为

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 的泰米尔数字(逻辑行直到右括号才结束)。

❹ 这个字符串在编译时与前一个拼接起来(参见 Python 语言参考手册中的“2.4.2. String literal concatenation”)。

❺ 字节序列只能用字节序列正则表达式搜索。

❻ 字符串模式 r'\d+' 能匹配泰米尔数字和 ASCII 数字。

❼ 字节序列模式 rb'\d+' 只能匹配 ASCII 字节中的数字。

❽ 字符串模式 r'\w+' 能匹配字母、上标、泰米尔数字和 ASCII 数字。

❾ 字节序列模式 rb'\w+' 只能匹配 ASCII 字节中的字母和数字。

图 4-4:运行示例 4-22 中的 ramanujan.py 脚本时的截图

示例 4-22 是随便举的例子,为的是说明一个问题:可以使用正则表达式搜索字符串和字节序列,但是在后一种情况中,ASCII 范围外的字节不会当成数字和组成单词的字母。

字符串正则表达式有个 re.ASCII 标志,它让 \w、\W、\b、\B、\d、\D、\s 和 \S 只匹配 ASCII 字符。详情参阅 re 模块的文档

另一个重要的双模式模块是 os。

4.9.2 os函数中的字符串和字节序列

GNU/Linux 内核不理解 Unicode,因此你可能发现了,对任何合理的编码方案来说,在文件名中使用字节序列都是无效的,无法解码成字符串。在不同操作系统中使用各种客户端的文件服务器,在遇到这个问题时尤其容易出错。

为了规避这个问题,os 模块中的所有函数、文件名或路径名参数既能使用字符串,也能使用字节序列。如果这样的函数使用字符串参数调用,该参数会使用 sys.getfilesystemencoding() 得到的编解码器自动编码,然后操作系统会使用相同的编解码器解码。这几乎就是我们想要的行为,与 Unicode 三明治最佳实践一致。

但是,如果必须处理(也可能是修正)那些无法使用上述方式自动处理的文件名,可以把字节序列参数传给 os 模块中的函数,得到字节序列返回值。这一特性允许我们处理任何文件名或路径名,不管里面有多少鬼符,如示例 4-23 所示。

示例 4-23 把字符串和字节序列参数传给 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.getfilesystemencoding() 返回的编解码器把 filename 解码成字符串;否则,返回未经修改的 filename 字符串。

在 Unix 衍生平台中,这些函数使用 surrogateescape 错误处理方式(参见下述附注栏)以避免遇到意外字节序列时卡住。Windows 使用的错误处理方式是 strict。

使用 surrogateescape 处理鬼符

Python 3.1 引入的 surrogateescape 编解码器错误处理方式是处理意外字节序列或未知编码的一种方式,它的说明参见“PEP 383 — Non-decodable Bytes in System Character Interfaces”。

这种错误处理方式会把每个无法解码的字节替换成 Unicode 中 U+DC00 到 U+DCFF 之间的码位(Unicode 标准把这些码位称为“Low Surrogate Area”),这些码位是保留的,没有分配字符,供应用程序内部使用。编码时,这些码位会转换成被替换的字节值,如示例 4-24 所示。

示例 4-24 使用 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 字节序列:各个代替码位还原成被替换的字节。

我们对字符串和字节序列的探讨到此结束。如果你坚持读到这里,恭喜你!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文