- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- 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.11 延伸阅读
Ned Batchelder 在 2012 年的 PyCon US 所做的演讲“Pragmatic Unicode—or—How Do I Stop the Pain?”非常出色。Ned 很专业,除了幻灯片和视频之外,他还提供了完整的文字记录。Esther Nam 和 Travis Fischer 在 PyCon 2014 做了一场精彩的演讲:“Character encoding and Unicode in Python: How to ( ╯ ° □ °) ╯︵ ┻━┻ with dignity” 幻灯片,[视频]。本章开头那句简短有力的话就是出自这次演讲:“人类使用文本,计算机使用字节序列。”本书的技术审校之一 Lennart Regebro 在“Unconfusing Unicode: What Is Unicode?”这篇短文中提出了“Useful Mental Model of Unicode(UMMU)”。Unicode 是个复杂的标准,Lennart 提出的 UMMU 是个很好的切入点。
Python 文档中的“Unicode HOWTO”一文从几个不同的角度对本章所涉及的话题做了讨论,从编码历史到句法细节、编解码器、正则表达式、文件名和 Unicode 的 I/O 最佳实践(即 Unicode 三明治),而且各节都给出了大量参考资料链接。Dive into Python 3 是一本非常优秀的书(Mark Pilgrim 著),其中第 4 章“Strings”对 Python 3 对 Unicode 的支持做了很好的介绍。此外,该书的第 15 章阐述了 Chardet 库从 Python 2 移植到 Python 3 的过程,这是一个宝贵的案例分析,从中可以看出,从旧的 str 类型转到新的 bytes 类型是造成迁移如此痛苦的主要原因,也是检测编码的库应该关注的重点。
如果你用过 Python 2,但是刚接触 Python 3,可以阅读 Guido van Rossum 写的“What's New in Python 3.0”,这篇文章简要列出了新版的 15 点变化,而且附有很多链接。Guido 开门见山地说道:“你自以为知道的二进制数据和 Unicode 知识全都变了。”Armin Ronacher 的博客文章“The Updated Guide to Unicode on Python”深入分析了 Python 3 中 Unicode 的一些陷阱(Armin 不是很热衷于 Python 3)。
《Python Cookbook(第 3 版)中文版》(David Beazley 和 Brian K. Jones 著)的第 2 章“字符串和文本”中有几个诀窍谈到了 Unicode 规范化、清洗文本,以及在字节序列上执行面向文本的操作。第 5 章涵盖文件和 I/O,“5.17 将字节数据写入文本文件”指出,任何文本文件的底层都有一个二进制流,如果需要可以直接访问。之后的“6.11 读写二进制结构的数组”用到了 struct 模块。
Nick Coghlan 的“Python Notes”博客中有两篇文章与本章的话题十分相关:“Python 3 and ASCII Compatible Binary Protocols”和“Processing Text Files in Python 3”。强烈推荐阅读。
Python 3.5 将为二进制序列引入新的构造方法和方法,而且会废弃目前使用的构造方法签名(参见“PEP 467—Minor API improvements for binary sequences”)。此外,Python 3.5 还会实现“PEP 461—Adding % formatting to bytes and bytearray”。
Python 支持的编码列表参见 codecs 模块文档的“Standard Encodings”一节。如果需要通过编程的方式获得那个列表,看看 CPython 源码中 /Tools/unicode/listcodecs.py 脚本是怎么做的。
Martijn Faassen 的文章“Changing the Python Default Encoding Considered Harmful”和 Tarek Ziadé的文章“sys.setdefaultencoding Is Evil”解释了为什么一定不能修改 sys.getdefaultencoding() 获取的编码,即便知道怎么做也不能改。
Unicode Explained(Jukka K. Korpela 著,O'Reilly 出版社)和 Unicode Demystified(Richard Gillam 著,Addison-Wesley 出版社)这两本书不是针对 Python 的,但在我学习 Unicode 相关概念时给了我很大的帮助。Victor Stinner 的著作 Programming with Unicode 是一本免费的自出版图书(遵守 CC BY-SA 协议),其中讨论了一般的 Unicode 话题,以及主流操作系统和几门编程语言(包括 Python)中的相关工具和 API。
W3C 网站中的“Case Folding: An Introduction”和“Character Model for the World Wide Web: String Matching and Searching”讨论了规范化相关的概念,前者是介绍性文章,后者则是以枯燥的标准用语写就的工作草案——“Unicode Standard Annex #15—Unicode Normalization Forms”也是这种风格。Unicode.org 网站中的“Frequently Asked Questions / Normalization”更容易理解, Mark Davis 写的“NFC FAQ”也是如此。Mark 是多个 Unicode 算法的作者,在我写作本书时,他还担任 Unicode 联盟的主席。
杂谈
“纯文本”是什么
对于经常处理非英语文本的人来说,“纯文本”并不是指“ASCII”。Unicode 词汇表是这样定义纯文本的:
只由特定标准的码位序列组成的计算机编码文本,其中不含其他格式化或结构化信息。
这个定义的前半句说得很好,但是我不同意后半句。HTML 就是包含格式化和结构化信息的纯文本格式,但它依然是纯文本,因为 HTML 文件中的每个字节都表示文本字符(通常使用 UTF-8 编码),没有任何字节表示文本之外的信息。.png 或 .xsl 文档则不同,其中多数字节表示打包的二进制值,例如 RGB 值和浮点数。在纯文本中,数字使用数字符号序列表示。
这本书是我用一种名为 AsciiDoc(很讽刺)的纯文本格式撰写的,它是 O'Reilly 优秀的图书出版平台 Atlas的工具链中的一部分。AsciiDoc 的源文件是纯文本,但用的是 UTF-8 编码,而不是 ASCII。如果不这样做的话,撰写本章必定痛苦不堪。姑且不管名称,AsciiDoc 是个很棒的工具。
Unicode 的世界正在不断扩张,但是有些边缘场景缺少支持工具。因此图 4-1、图 4-3 和图 4-4 中的内容要使用图像,因为渲染本书的字体中缺少一些我想展示的字符。不过,Ubuntu 14.04 和 OS X 10.9 的终端能正确显示,包括“mojibake”(文字化け)这个日文的词。
捉摸不透的 Unicode
讨论 Unicode 规范化时,我经常使用“往往”“多数”和“通常”等不确定的修饰语。很遗憾,我不能提供更可靠的建议,因为 Unicode 规则有很多例外,很难百分之百确定。
例如,μ(微符号)是“兼容字符”,而 Ω(欧姆)和 Å(埃)符号却不是。这种差别是有真实影响的:NFC 规范化形式(推荐用于文本匹配)会把 Ω(欧姆)替换成 Ω(大写希腊字母欧米加),把 Å(埃)替换成 Å(上有圆圈的大写字母 A)。但是,作为“兼容字符”的 μ(微符号)不会替换成视觉等效的 μ(小写希腊字母 μ);不过在使用更极端的 NFKC 或 NFKD 规范化形式时会替换,但这是有损转换。
我能理解为什么把 μ(微符号)纳入 Unicode,因为 latin1 编码中有它,如果换成希腊字母 μ,会破坏两种编码之间的转换。说到底,这就是微符号是“兼容字符”的原因。但是,如果是由于兼容原因而没把欧姆和埃符号纳入 Unicode,那为什么这两个符号要存在? Unicode 已经为 GREEK CAPITAL LETTER OMEGA 和 LATIN CAPITAL LETTER A WITH RING ABOVE 分配了码位,它们的外观一样,而且 NFC 规范化形式会替换它们。想想看吧。
研究 Unicode 几小时之后,我猜测的原因是:Unicode 异常复杂,充满特殊情况,而且要覆盖各种人类语言和产业标准策略。
在 RAM 中如何表示字符串
Python 官方文档对字符串的码位在内存中如何存储避而不谈。毕竟,这是实现细节。理论上,怎么存储都没关系:不管内部表述如何,输出时每个字符串都要编码成字节序列。
在内存中,Python 3 使用固定数量的字节存储字符串的各个码位,以便高效访问各个字符或切片。
在 Python 3.3 之前,编译 CPython 时可以配置在内存中使用 16 位或 32 位存储各个码位。16 位是“窄构建”(narrow build),32 位是“宽构建”(wide build)。如果想知道用的是哪个,要查看 sys.maxunicode 的值:65535 表示“窄构建”,不能透明地处理 U+FFFF 以上的码位。“宽构建”没有这个限制,但是消耗的内存更多:每个字符占 4 个字节,就算是中文象形文字的码位大多数也只占 2 个字节。这两种构建没有高下之分,应该根据自己的需求选择。
从 Python 3.3 起,创建 str 对象时,解释器会检查里面的字符,然后为该字符串选择最经济的内存布局:如果字符都在 latin1 字符集中,那就使用 1 个字节存储每个码位;否则,根据字符串中的具体字符,选择 2 个或 4 个字节存储每个码位。这是简述,完整细节参阅“PEP 393—Flexible String Representation”。
灵活的字符串表述类似于 Python 3 对 int 类型的处理方式:如果一个整数在一个机器字中放得下,那就存储在一个机器字中;否则解释器切换成变长表述,类似于 Python 2 中的 long 类型。这种聪明的做法得到推广,真是让人欢喜!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论