PEP 414 – Python 3.3 中的显式 Unicode 字面量
- 作者:
- Armin Ronacher <armin.ronacher at active-4.com>,Alyssa Coghlan <ncoghlan at gmail.com>
- 状态:
- 最终
- 类型:
- 标准跟踪
- 创建:
- 2012-02-15
- Python 版本:
- 3.3
- 历史记录:
- 2012-02-28,2012-03-04
- 决议:
- 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 3 上运行的现有 Python 2 代码行数。
具体来说,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 Web 生态系统的许多部分一直在共同努力支持 Python 3,而不会对其现有的开发者和用户社区造成负面影响。
来自这些社区中关键开发人员(包括 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 的采用
此抱怨很有趣,因为它隐含地承认此 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 3 不应该为了支持从 Python 2 的移植而变得更差
这确实是 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 的“原生字符串”概念是一个丑陋的黑客
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 标识符(作为属性、字典键、类名、模块名、导入引用等)
- 大多数情况下以及 urllib/http 服务器中的 HTTP 头部的 URL
- 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
上次修改时间: 2023-10-11 12:05:51 GMT