PEP 497 – 向后兼容的标准机制
- 作者:
- Ed Schofield <ed at pythoncharmers.com>
- PEP 代理人:
- Brett Cannon <brett at python.org>
- 状态:
- 已拒绝
- 类型:
- 流程
- 创建日期:
- 2015年8月4日
拒绝通知
指导委员会决定,此提案的__past__
方面对于潜在收益来说过于复杂。关于更强的向后兼容性要求的其他方面应由PEP 387来处理。
范围
本PEP是对PEP 5、PEP 236和PEP 387的补充,并具有相似的目标。
本PEP解释了支持PEP 5“语言演进指南”的额外兼容性机制的必要性。PEP 236“回到__future__
”引入了一个支持PEP 5的向前兼容机制,但指出新的向后兼容机制超出了该PEP的范围。一个相关的(正在进行的)PEP引入了这样的向后兼容机制。
PEP 5,“语言演进指南”,指出“本PEP [PEP 5]不取代或排除其他兼容性策略,例如动态加载向后兼容的解析器。”
背景
来自PEP 236:“时不时地,Python会对核心语言构造的声明式语义进行不兼容的更改,或以某种方式改变其偶然的(依赖于实现的)行为。虽然这从不是随意进行的,并且总是以长期改进语言为目标,但在短期内,它是有争议且令人不安的。PEP 5,语言演进指南,提出了缓解痛苦的方法,而本PEP [PEP 236]引入了一些支持它的机制。”
同样来自PEP 236:“future_statement的目的是让那些及时跟上最新版本的人的生活更轻松。如果你不跟上,我们不恨你,但你的问题会更难解决,遇到这些问题的人需要写一个PEP来解决它们。future_statement面向的是不同的受众。”
当前状况
当对核心语言语法或语义进行不兼容的更改时,Python目前提供了future_statement机制,为向后兼容性提供向前兼容性,直到强制执行新语法或语义的版本发布为止,但在此版本之后没有提供相应的标准机制来提供向后兼容性。
问题
这种不对称性带来的后果是,对于一个破坏性更改,旧的(破坏前的)Python解释器版本比新的(破坏性的)版本功能更强大;旧的解释器可以同时使用设计于更改之前以及较新的代码,而新的解释器只能使用已升级以支持更改后的功能的代码。
例如,考虑2001年PEP 238中引入的除法运算符的更改,这发生在PEP 236引入future_statement机制之后不久。PEP 238为Python 2.x系列的“真实除法”提供了一套有用的向前兼容机制,但没有包含任何在“真实除法”首次在Python 3.0中强制执行之后的向后兼容机制。自3.0以来的Python版本不提供向后兼容机制,例如from __past__ import division
,用于期望旧的“经典除法”语义的代码,而3.0之前的Python版本则同时支持“经典除法”代码,也支持期望“真实除法”的代码的向前兼容性。由此产生的进一步后果是,就各种除法相关的Python代码而言,“最兼容”的解释器是Python 2.7,即在破坏性更改首次强制执行之前的版本。
向后兼容性是“下游升级”的推动力
与此情况形成对比的是,较新的应用程序软件版本(如办公套件)在加载不同版本数据文件格式的支持方面,通常比早期版本更强大。通常的模式是,较新的应用程序版本可以无缝加载其较新或较旧数据格式的数据,并且较新版本默认以较新格式保存数据。较新的应用程序软件版本默认情况下倾向于向后兼容。向前兼容性相对较少。
这种策略使用户(使用较新应用程序软件的用户)相对于(使用旧软件的用户)处于优势地位,因为旧软件通常无法加载新格式的数据。有时,使用较新软件应用程序版本的用户可以通过显式选择此选项来导出旧版本数据。在这些情况下,由此实现的向前兼容性可能完全正常,也可能不完全正常;某些功能可能会丢失,或者结果可能不尽人意。因此,升级很容易,而降级则更难。
这种“新颖的吸引人功能加上向后兼容性功能”的策略在众多用户中产生的效果是,用户个体升级自身应用程序版本的自然压力会逐渐累积,并且,用户之间交换数据文件的数量越多,这种压力就越剧烈。
提案 - 第一部分
本PEP提出了两个具体且相关的提案。第一个是
示例
作为如何应用本PEP的一个例子,如果“真实除法”PEP(238)的最新修订版今天被提出,它将被认为是不完整的。PEP 238提到了该提案引起的“严重的向后兼容性问题”,并在摘要和API更改部分描述了多项向前兼容性措施。它还提到了在c.l.py上提出的一些向后兼容性想法,包括“使用from __past__ import division
在一个模块中使用经典除法语义”,但它并没有将任何向后兼容性计划作为提案的一部分。
如果本PEP被接受,人们会期望像PEP 238这样的提案,由于其大规模的兼容性影响,还将附带一个向后兼容计划,使未来Python版本在破坏性更改生效后,用户能够在其代码中轻松地重新启用经典除法行为。
提案 - 第二部分
第二个提案是
Python提供一个标准的向后兼容机制,与__future__
模块提供的向前兼容机制并行。
为方便参考,本文档在此将其称为“__past__
”机制,尽管它不一定具有__future__
模块和future_statement
机制的所有特征。
“__past__
”机制的具体形式和实现是另一个(正在进行的)PEP的主题。然而,本PEP建议此“__past__
”机制的设计应满足与PEP 296中为__future__
概述的类似标准。具体而言:
a. 它应允许 individual 模块指定要从旧Python版本重新启用的过时行为,按模块进行。
b. 它应足够灵活,以便Python 3.6+和早期版本的点发布能够为调用“__past__
”机制的用户模块重新引入与旧Python语法或语义的向后兼容性。
c. 应该能够运行经过修改以调用“__past__
”行为的旧代码,在没有特定“__past__
”功能知识,甚至不知道向后兼容性“__past__
”机制存在的旧Python版本(如2.x)上运行。
反例
以下是一些可能违反这些标准的“__past__
”机制的实现方式:
a. 导入钩子。这些通常无法按模块工作;相反,它们会递归应用于从模块内导入的所有新模块。
b. Python 3.6 的新语法或新语义,与先前版本不兼容。
c. Python 3.6 中添加到Python标准库的某个模块的函数,该函数在先前Python版本中具有相同的名称。
好处
采纳此提案对Python开发团队的好处是,未来的向后不兼容更改可能会干扰较小,如果这些更改中的每一个都有一个对应的“__past__
”功能,并且该功能已实现,用户可以在未来的Python版本中轻松调用。这有助于语言更快、更有效地演进,以纠正设计错误。
对保守用户的好处是显而易见的:他们可以通过在每个模块中添加一个“__past__
”咒语(可能是一行),为他们的代码添加对最新、最吸引人的、破坏兼容性的Python版本的支持,并且这可以自动化。然后,他们可以升级他们的解释器到最新版本,并获得最新的、闪亮的新Python功能。
对社区的好处是,如果一万个用户依赖于包XYZ,而包XYZ可以轻松地支持最新的Python版本,那么这在一万个用户也可以快速升级到最新的Python版本,而不会被延迟等待包XYZ完成此操作。
问答
Q1:本PEP是否要求Python永远保留每种向后不兼容特性的两种可能语义集?
A1:绝不是。遗留特性仍然可以在适当的时候被淘汰——也就是说,当大多数用户群已迁移到较新的Python版本时。本PEP仅建议将用于兼容性的开发工作重点从100%向前转移到至少50%向后。向后兼容性是允许用户群采用最新Python解释器版本的两个概念中更强大的那个。
请注意,自从大多数用户不再关心非嵌套作用域的向后兼容性以来已经过去了很长时间,因为大多数用户已经舒适地迁移到了Python 2.1之后。
Q2:但Python开发团队已经不堪重负,没有足够的资源来实现/维护额外的复杂性!
A2:Python开发团队可以请求社区开发者挺身而出,为他们关心的遗留语言特性维护Python的向后兼容性。当社区不再关心某个特定的过时行为时,Python开发团队也可以不再关心。
“__past__
”机制可能被设计成可由社区扩展,例如作为一个标准的但“受祝福的”PyPI包,以减轻核心开发者的负担。
Q3:向后兼容性功能不会导致Python中出现大量的冗余、臃肿和包袱吗?
A3:不一定。首先,Python中新的破坏兼容性特性的提案可以部分根据其相关“__past__
”功能实现的简洁性和可维护性来评估。
其次,一些旧特性可以很容易地提供向后兼容性。以Python 3.0之前的“经典除法”行为为例。“python-future
”项目包含一个在函数future.utils.old_div
中的经典除法兼容实现。
def old_div(a, b):
"""
Equivalent to ``a / b`` on Python 2 without ``from __future__ import
division``.
"""
if isinstance(a, numbers.Integral) and isinstance(b, numbers.Integral):
return a // b
else:
return a / b
将这样一个函数与Python 3.x版本捆绑在一起,并提供一个简单的机制来调用它,以处理在适当的“__past__
”调用后出现的每次a / b
,这并不算很麻烦。
Q4:性能如何?新Python版本的性能会不会因为遗留特性的存在而受到影响?
A4:这可以根据具体情况进行评估。主要的潜在担忧是,新默认行为的性能不会因为遗留选项的存在而受到不应有的影响。受“__past__
”调用影响下的性能是次要的。
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0497.rst