PEP 414 – Python 3.3 的显式 Unicode 字面量
- 作者:
- Armin Ronacher <armin.ronacher at active-4.com>, Alyssa Coghlan <ncoghlan at gmail.com>
- 状态:
- 最终版
- 类型:
- 标准跟踪
- 创建日期:
- 2012 年 2 月 15 日
- Python 版本:
- 3.3
- 发布历史:
- 2012 年 2 月 28 日, 2012 年 3 月 4 日
- 决议:
- Python-Dev 消息
摘要
本文档提议将 Python 2.x 中显式 unicode 字面量重新集成到 Python 3.x 语言规范中,以减少将支持 Unicode 的 Python 2 应用程序移植到 Python 3 时所需的更改量。
BDFL 声明
本 PEP 已被 Python 3.3 正式接受
我接受这个 PEP。它几乎是无害的。就这样吧。
提案
本 PEP 提议 Python 3.3 恢复对 Python 2 的 Unicode 字面量语法的支持,从而大幅增加支持 Unicode 的应用程序中现有 Python 2 代码的行数,这些代码将在 Python 3 上无需修改即可运行。
具体来说,Python 3 中字符串字面量前缀的定义将扩展为允许使用
"u" | "U"
除了目前支持的
"r" | "R"
以下都将表示普通的 Python 3 字符串
'text'
"text"
'''text'''
"""text"""
u'text'
u"text"
u'''text'''
u"""text"""
U'text'
U"text"
U'''text'''
U"""text"""
没有提议更改 Python 3 实际的 Unicode 处理,只更改字符串字面量的可接受形式。
“原始”Unicode 字面量的排除
Python 2 支持“原始”Unicode 字面量的概念,它不符合原始字符串的传统定义:\uXXXX 和 \UXXXXXXXX 转义序列仍然由编译器处理,并在创建相关联的 Unicode 对象时转换为适当的 Unicode 码点。
Python 3 没有对应的概念——编译器对原始字符串字面量的内容进行*不*预处理。这与 Python 2 中 8 位原始字符串字面量的行为相匹配。
由于此类字符串很少使用,如果允许在 Python 3 中会被不同地解释,因此决定完全排除它们是更好的选择。因此,使用它们的代码在 Python 3 上仍会立即失败(出现语法错误),而不是可能产生不同的输出。
为了获得可在 Python 2 和 Python 3 上运行的等效行为,可以使用普通的 Unicode 字面量(在字符串内进行适当的额外转义),或者通过字符串连接或字符串格式化来组合字符串的原始部分和需要使用 Unicode 转义序列的部分。
请注意,当在 Python 2 中使用 from __future__ import unicode_literals 时,名义上的“原始”Unicode 字符串字面量将处理 \uXXXX 和 \UXXXXXXXX 转义序列,就像显式标记为“原始 Unicode”前缀的 Python 2 字符串一样。
基本原理
随着适用于 Python 3.2 的 Web 服务网关接口 (WSGI) 规范 (PEP 3333) 的 Python 3 兼容版本的发布,Python 网络生态系统的许多部分都在共同努力支持 Python 3,同时不 adversely 影响其现有开发人员和用户社区。
来自这些社区中关键开发人员的一项主要反馈,包括 Chris McDonough (WebOb, Pyramid)、Armin Ronacher (Flask, Werkzeug)、Jacob Kaplan-Moss (Django) 和 Kenneth Reitz (requests) 是,要求更改应用程序中*每个* Unicode 字面量的拼写(无论如何实现)是移植工作的关键障碍。
特别是,与许多其他 Python 3 更改不同,它不是框架和库作者可以轻易地为其用户处理的更改。大多数用户根本不关心 Python 语言规范的“纯粹性”,他们只是希望他们的网站和应用程序尽可能地正常运行。
虽然 Python Web 社区在强调这一问题上最为积极,但预计其他高度支持 Unicode 的领域(如 GUI 开发)在它们(及其社区)开始努力支持 Python 3 时,可能会遇到类似的问题。
常见异议
抱怨:本 PEP 可能会损害 Python 3.2 的采用
这个抱怨很有趣,因为它 tacitly 承认这个 PEP *将*使将支持 Unicode 的 Python 2 应用程序移植到 Python 3 更容易。
有许多现有的 Python 社区愿意忍受现有移植工具套件强加的限制,或者充分更新他们的 Python 2 代码库以使问题最小化。
本 PEP 不适用于这些社区。相反,它专门旨在帮助那些*不*想忍受这些困难的人。
然而,由于该提案是对语言语法进行相对较小的调整,且没有语义变化,因此将其作为第三方导入钩子来支持是可行的。虽然此类导入钩子会带来一些导入时间开销,并且需要每个需要它的应用程序采取额外步骤来安装该钩子,但它允许面向 Python 3.2 的应用程序使用原本只能在 Python 3.3+ 上运行的库和框架,因为它们使用了 unicode 字面量前缀。
其中一个导入钩子项目是 Vinay Sajip 的 uprefix [4]。
对于那些喜欢提前转换代码而不是在导入时即时转换的人,Armin Ronacher 正在开发一个在安装时而不是在导入时运行的钩子 [5]。
当然,结合这两种方法也是可能的。例如,导入钩子可用于本地开发期间的快速编辑-测试周期,但安装钩子用于 Python 3.2 上的持续集成任务和部署。
本节所述的方法可能有用,例如,对于希望在 Ubuntu 12.04 LTS 版本上针对 Python 3 的应用程序,该版本将附带 Python 2.7 和 3.2 作为官方支持的 Python 版本。
抱怨:不应为了支持从 Python 2 移植而使 Python 3 变差
这确实是 Python 3 的关键设计原则之一。然而,作为 Python 整体的关键设计原则之一是“实用胜于纯粹”。如果我们要给第三方开发人员施加重大负担,我们就应该有一个充分的理由这样做。
在大多数情况下,向后不兼容的 Python 3 更改的理由要么是为了提高代码的正确性(例如,更严格的二进制和文本数据的默认分离,以及必要时整数除法升级为浮点数),要么是为了减少典型的内存使用量(例如,更多地使用迭代器和视图而非具体列表),要么是为了消除分散注意力的烦恼,这些烦恼使 Python 代码更难阅读而没有增加其表达力(例如,基于逗号的捕获异常命名语法)。基于此类推理的更改*不会*被撤销,无论 Python 2 开发人员试图过渡到 Python 3 的反对意见如何。
在许多情况下,Python 2 出于历史原因提供了两种做法。例如,不相等既可以通过 != 也可以通过 <> 来测试,整数字面量可以指定可选的 L 后缀。这些冗余在 Python 3 中已被消除,这减少了语言的总体大小并提高了开发人员之间的一致性。
在最初的 Python 3 设计(包括 Python 3.2)中,unicode 字面量的显式前缀语法被认为属于这一类,因为它在 Python 3 中是完全不必要的。然而,这些其他情况与 unicode 字面量之间的区别在于,unicode 字面量前缀在 Python 2 代码中*不*是冗余的:它是一个程序上重要的区别,需要以某种方式保留以避免信息丢失。
虽然创建了移植工具来帮助过渡(参见下一节),但它仍然给 Python 2 中大量使用 unicode 字符串的用户带来了额外的负担,仅仅是为了让未来学习 Python 3 的开发人员不必被告知“由于历史原因,字符串字面量可能有一个可选的 u 或 U 前缀。永远不要自己使用它,它只是为了帮助从早期版本的语言中移植。”
许多学习 Python 2 的学生也收到了关于字符串异常的类似警告,但并没有感到困惑,也没有在他们作为 Python 开发者的成长中受到不可弥补的阻碍。对于这个特性来说,也会是同样的情况。
Python 3 *仍然*允许字节字面量和原始字节及字符串字面量使用 B 和 R 前缀的大写变体,这一事实进一步强化了这一点。如果由于字符串前缀变体而引起的潜在混淆如此重要,为什么没有呼声要求在 Python 3 中消除所有其他冗余的同时,也消除这些冗余前缀呢?
就像对字符串异常的支持通过正常的弃用过程从 Python 2 中消除一样,对冗余字符串前缀字符(特别是 B、R、u、U)的支持最终也可能从 Python 3 中消除,无论当前是否接受本 PEP。然而,这种更改很可能只有在支持 Python 2.7 的第三方库像今天支持 Python 2.2 或 2.3 的库一样普遍时才会发生。
抱怨:WSGI“原生字符串”概念是一个丑陋的 HACK
Unicode 字面量的移除在 Web 开发社区中引起如此关注的原因之一是,更新后的 WSGI 规范不得不做出一些妥协,以最大程度地减少对提供 WSGI 兼容接口的现有 Web 服务器的干扰(这被认为是必要的,以便使更新后的标准成为 Web 应用程序作者和 Web 框架开发人员的可行目标)。
其中一个妥协就是“原生字符串”的概念。WSGI 定义了三种不同类型的字符串
- 文本字符串:在 Python 2 中作为
unicode处理,在 Python 3 中作为str处理 - 原生字符串:在 Python 2 和 Python 3 中都作为
str处理 - 二进制数据:在 Python 2 中作为
str处理,在 Python 3 中作为bytes处理
一些开发者认为 WSGI 的“原生字符串”是一个丑陋的 hack,因为它们被*明确地*记录为仅用于 latin-1 解码的“文本”,而不管底层数据的实际编码。使用这种方法绕过了 Python 3 数据模型的许多更新,这些更新旨在鼓励正确处理文本编码。然而,由于问题域的特殊细节,它通常是有效的——Web 服务器和 Web 框架开发者是最了解在使用 HTTP 和相关协议时二进制数据和文本之间的界限有多模糊,以及在操作编码文本数据时理解所用编码的重要性的人之一。在*应用*级别,这些细节大部分被 Web 框架和支持库(无论在 Python 2 *还是* Python 3 中)向开发者隐藏。
在实践中,原生字符串是一个有用的概念,因为有一些 API(无论是标准库还是第三方框架和包)以及一些内部解释器细节主要是为与 str 配合使用而设计的。这些组件通常不支持 Python 2 中的 unicode 或 Python 3 中的 bytes,或者,如果它们支持,则需要额外的编码细节和/或施加不适用于 str 变体的约束。
最好使用实际的 str 实例处理的接口示例包括
- Python 标识符(作为属性、字典键、类名、模块名、导入引用等)
- 大部分 URL 以及 urllib/http 服务器中的 HTTP 头
- WSGI 环境键和 CGI 继承值
- 用于动态编译和 AST hack 的 Python 源代码
- 异常消息
__repr__返回值- 首选文件系统路径
- 首选操作系统环境
在 Python 2.6 和 2.7 中,这些区别最自然地表达如下:
u"": 文本字符串 (unicode)"": 原生字符串 (str)b"": 二进制数据 (str,也别名为bytes)
在 Python 3 中,latin-1 解码的原生字符串与任何其他文本字符串没有区别
"": 文本字符串 (str)"": 原生字符串 (str)b"": 二进制数据 (bytes)
如果使用 from __future__ import unicode_literals 来修改 Python 2 的行为,那么,结合 n() 的适当定义,这种区别可以表示为:
"": 文本字符串n(""): 原生字符串b"": 二进制数据
(虽然 n=str 适用于简单情况,但由于非 ASCII 源编码,有时可能会出现问题)
在 Python 2 和 Python 3 的公共子集中(通过适当指定源编码和定义 u() 和 b() 辅助函数),它们可以表示为
u(""): 文本字符串"": 原生字符串b(""): 二进制数据
最后一种方法是唯一支持 Python 2.5 及更早版本的变体。
在所有替代方案中,Python 2.6 和 2.7 中目前支持的格式是迄今为止最简洁的方法,它清晰地区分了三种期望的行为。通过本 PEP,这种格式也将在 Python 3.3+ 中得到支持。它还将通过使用导入和安装钩子在 Python 3.1 和 3.2 中得到支持。虽然可能性显著降低,但也可以设想,钩子可以进行调整,以允许在 Python 2.5 上使用 b 前缀。
抱怨:现有工具应该对每个人都足够好
开发人员普遍表达的一种观点是,他们已经成功将应用程序移植到 Python 3,大致意思是“如果你觉得很难,那你做错了”或“没那么难,试试就知道了!”。虽然这无疑是无意的,但这些回应都起到了告诉那些指出当前移植工具集不足之处的人“移植工具没问题,你只是太差劲,不知道如何正确使用它们”的效果。
这些回应完全偏离了人们抱怨的重点。导致本 PEP 的反馈并非源于人们抱怨移植不可能。相反,这些反馈来自已经成功*完成*移植的人,他们反对的是,他们发现针对他们需要移植的应用程序类别(特别是支持 Unicode 的 Web 框架和支持库),这种体验非常*不愉快*。
这是一个主观的评估,这也是为什么 Python 3 移植工具生态系统是一个“做一件事的唯一明显方式”哲学*不*适用的案例。虽然最初的设想是“在 Python 2 中开发,使用 2to3 转换,然后两者都测试”将是并行开发两个版本的标准方式,但在实践中,不同项目和开发社区的需求已被证明足够多样化,因此已经设计了各种方法,允许每个组选择最适合其需求的方法。
Lennart Regebro 对可用的迁移策略进行了出色的概述 [2],官方移植指南也提供了类似的评论 [3]。(请注意,自 Lennart 撰写其概述以来,官方指南已软化为“这取决于您的具体情况”)。
然而,这两份指南都是基于这样的基本假设:所有涉及的开发人员*已经*致力于支持 Python 3。它们没有考虑到这种改变的*社会*方面,当您与一个可能对没有明确好处的干扰不特别容忍的用户群体打交道,或者试图说服专注于 Python 2 的上游开发人员接受仅仅是为了提高 Python 3 前向兼容性的补丁时。
使用当前的移植工具集,*每个*迁移策略都会导致项目中*每个* Unicode 字面量的更改。无一例外。它们将被转换为无前缀字符串字面量(如果项目决定采用 unicode_literals 导入)或者转换为像 u("text") 这样的转换器调用。
如果采用 unicode_literals 导入方法,但未在整个项目上同时采用,则裸字符串字面量的含义可能会变得令人烦恼地模糊。这个问题对于像 Django 站点这样的*聚合*软件尤其有害——在这种情况下,有些文件可能最终使用 unicode_literals 导入,而另一些则可能不使用,从而产生明确的混淆可能性。
虽然这些问题在技术层面显然是可以解决的,但在社会层面,它们是完全不必要的干扰。开发人员的精力应该留给解决与 Python 3 转换相关的*真正*技术难题(比如区分 8 位文本字符串和二进制数据)。他们不应该仅仅因为他们*已经*在 Python 2 中明确识别了他们的 Unicode 字符串而受到额外的代码更改(即使是自动化的)的惩罚。
Armin Ronacher 创建了一个 2to3 的实验性扩展,它只将 Python 代码现代化到在 Python 2.7 或更高版本上运行的程度,并得到跨版本兼容性 six 库的支持。这个工具被称为 python-modernize [1]。目前,这个工具生成的差异将影响转换源代码中的每个 Unicode 字面量。这将引起上游开发人员对接受此类更改的合理担忧,以及框架*用户*被要求更改其应用程序的担忧。
然而,通过消除 Unicode 字面量语法更改带来的噪音,许多项目可以通过运行 python-modernize 并应用推荐的更改,干净地、(相对)无争议地实现与 Python 3.3+ 的向前兼容。
参考资料
版权
本文档已置于公共领域。
来源: https://github.com/python/peps/blob/main/peps/pep-0414.rst
最后修改: 2025-02-01 08:59:27 GMT