返回介绍

4.11 延伸阅读

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

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 技术交流群。

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

发布评论

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