- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- Python 术语表
- Python 版本表
- 排版约定
- 使用代码示例
- 第一部分 序幕
- 第 1 章 Python 数据模型
- 第二部分 数据结构
- 第 2 章 序列构成的数组
- 第 3 章 字典和集合
- 第 4 章 文本和字节序列
- 第三部分 把函数视作对象
- 第 5 章 一等函数
- 第 6 章 使用一等函数实现设计模式
- 第 7 章 函数装饰器和闭包
- 第四部分 面向对象惯用法
- 第 8 章 对象引用、可变性和垃圾回收
- 第 9 章 符合 Python 风格的对象
- 第 10 章 序列的修改、散列和切片
- 第 11 章 接口:从协议到抽象基类
- 第 12 章 继承的优缺点
- 第 13 章 正确重载运算符
- 第五部分 控制流程
- 第 14 章 可迭代的对象、迭代器和生成器
- 14.1 Sentence 类第1版:单词序列
- 14.2 可迭代的对象与迭代器的对比
- 14.3 Sentence 类第2版:典型的迭代器
- 14.4 Sentence 类第3版:生成器函数
- 14.5 Sentence 类第4版:惰性实现
- 14.6 Sentence 类第5版:生成器表达式
- 14.7 何时使用生成器表达式
- 14.8 另一个示例:等差数列生成器
- 14.9 标准库中的生成器函数
- 14.10 Python 3.3 中新出现的句法:yield from
- 14.11 可迭代的归约函数
- 14.12 深入分析 iter 函数
- 14.13 案例分析:在数据库转换工具中使用生成器
- 14.14 把生成器当成协程
- 14.15 本章小结
- 14.16 延伸阅读
- 第 15 章 上下文管理器和 else 块
- 第 16 章 协程
- 第 17 章 使用期物处理并发
- 第 18 章 使用 asyncio 包处理并发
- 第六部分 元编程
- 第 19 章 动态属性和特性
- 第 20 章 属性描述符
- 第 21 章 类元编程
- 结语
- 延伸阅读
- 附录 A 辅助脚本
- Python 术语表
- 作者简介
- 关于封面
4.9 支持字符串和字节序列的双模式 API
标准库中的一些函数能接受字符串或字节序列为参数,然后根据类型展现不同的行为。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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论