PEP 236 – 回到 __future__
- 作者:
- Tim Peters <tim.peters at gmail.com>
- 状态:
- 最终
- 类型:
- 标准跟踪
- 创建:
- 2001年2月26日
- Python 版本:
- 2.1
- 历史记录:
- 2001年2月26日
动机
有时,Python 会对核心语言结构的已声明语义进行不兼容的更改,或者以某种方式更改其意外的(依赖于实现的)行为。虽然这绝非随意进行,并且始终是为了从长远来看改进语言,但在短期内,它是有争议且破坏性的。
PEP 5,语言演变指南建议了一些减轻痛苦的方法,而本 PEP 引入了一些支持该方法的机制。
PEP 227,静态嵌套作用域是第一个应用,并将在此处用作示例。
意图
[注意:这是策略,因此最终应迁移到 PEP 5]
当对核心语言语法或语义进行不兼容的更改时
- 引入更改的版本 C 不会默认更改语法或语义。
- 确定一个未来的版本 R,其中将强制执行新的语法或语义。
- 在 PEP 230,警告框架中描述的机制用于尽可能生成警告,关于其含义可能在版本 R 中[1]发生变化的结构或操作。
- 新的 future_statement(见下文)可以显式地包含在模块 M 中,以请求模块 M 中的代码在当前版本 C 中使用新的语法或语义。
因此,旧代码默认情况下至少可以继续工作一个版本,尽管它可能会开始生成新的警告消息。在此期间,可以迁移到新的语法或语义,使用 future_statement 使包含它的模块的行为就像新语法或语义已经强制执行一样。
请注意,除非新特性可能破坏现有代码,否则无需在其中涉及 future_statement 机制;完全向后兼容的添加可以并且应该在没有相应的 future_statement 的情况下引入。
语法
future_statement 只是一个使用保留模块名称 __future__
的 from/import 语句
future_statement: "from" "__future__" "import" feature ["as" name]
(","feature ["as" name])*
feature: identifier
name: identifier
此外,所有 future_statements 必须出现在模块的顶部附近。future_statement 之前只能出现以下几行
- 模块文档字符串(如果有)。
- 注释。
- 空行。
- 其他 future_statements。
示例
"""This is a module docstring."""
# This is a comment, preceded by a blank line and followed by
# a future_statement.
from __future__ import nested_scopes
from math import sin
from __future__ import alabaster_weenoblobs # compile-time error!
# That was an error because preceded by a non-future_statement.
语义
future_statement 在编译时会被识别并进行特殊处理:核心结构的语义更改通常通过生成不同的代码来实现。甚至可能出现新特性引入新的不兼容语法(例如新的保留字)的情况,在这种情况下,编译器可能需要以不同的方式解析模块。此类决策不能推迟到运行时。
对于任何给定的版本,编译器都知道已定义了哪些特性名称,如果 future_statement 包含编译器未知的特性[2],则会引发编译时错误。
直接运行时语义与任何 import
语句相同:有一个标准模块 __future__.py
,稍后将对其进行描述,它将在执行 future_statement 时以通常的方式导入。
有趣的运行时语义取决于模块中出现的 future_statement(s)“导入”的特定特性(s)。
请注意,以下语句没有任何特殊之处
import __future__ [as name]
这不是 future_statement;它是一个普通的 import 语句,没有特殊的语义或语法限制。
示例
考虑以下代码,在文件 scope.py 中
x = 42
def f():
x = 666
def g():
print "x is", x
g()
f()
在 2.0 下,它打印
x is 42
嵌套作用域(PEP 227)将在 2.1 中引入。但在 2.1 下,它仍然打印
x is 42
并且还会生成一个警告。
在 2.2 中,以及在 2.1 中如果 from __future__ import nested_scopes
包含在 scope.py
的顶部,它打印
x is 666
标准模块 __future__.py
Lib/__future__.py
是一个真正的模块,它有三个目的
- 避免混淆分析 import 语句并期望找到其正在导入的模块的现有工具。
- 确保 future_statements 至少在 2.1 之前的版本下运行时会产生运行时异常(
__future__
的导入将失败,因为在 2.1 之前没有名为该名称的模块)。 - 记录不兼容更改何时引入,以及何时将强制执行或已强制执行。这是一种可执行文档的形式,可以通过导入
__future__
并检查其内容以编程方式进行检查。
__future__.py
中的每个语句都具有以下形式
FeatureName = "_Feature(" OptionalRelease "," MandatoryRelease ")"
通常,OptionalRelease < MandatoryRelease,并且两者都是与 sys.version_info
形式相同的 5 元组
(PY_MAJOR_VERSION, # the 2 in 2.1.0a3; an int
PY_MINOR_VERSION, # the 1; an int
PY_MICRO_VERSION, # the 0; an int
PY_RELEASE_LEVEL, # "alpha", "beta", "candidate" or "final"; string
PY_RELEASE_SERIAL # the 3; an int )
OptionalRelease 记录接受
from __future__ import FeatureName
的第一个版本。
在尚未发生的MandatoryReleases 的情况下,MandatoryRelease 预测特性将成为语言一部分的版本。
否则,MandatoryRelease 记录特性成为语言一部分的时间;在该版本或之后的版本中,模块不再需要
from __future__ import FeatureName
使用该特性,但可以继续使用此类导入。
MandatoryRelease 也可能为 None
,这意味着已放弃计划的特性。
class _Feature
的实例有两个对应的方法,.getOptionalRelease()
和 .getMandatoryRelease()
。
永远不会从 __future__.py
中删除特性行。
示例行
nested_scopes = _Feature((2, 1, 0, "beta", 1), (2, 2, 0, "final", 0))
这意味着
from __future__ import nested_scopes
将在 2.1b1 或之后的版本中工作,并且嵌套作用域旨在从 2.2 版本开始强制执行。
已解决的问题:运行时编译
一些 Python 特性可以在模块的运行时编译代码
exec
语句。execfile()
函数。compile()
函数。eval()
函数。input()
函数。
由于包含 future_statement 并命名特性 F 的模块 M 明确请求当前版本在 F 方面表现得像未来版本一样,因此从 M 中传递给这些函数之一的文本动态编译的任何代码也可能应该使用与 F 关联的新语法或语义。2.1 版本确实以这种方式运行。
不过,这并不总是想要的。例如,doctest.testmod(M)
编译从 M 中字符串获取的示例,这些示例应使用 M 的选项,而不一定使用 doctest 模块的选项。在 2.1 版本中,这是不可能的,并且还没有提出任何解决此问题的方案。注意:PEP 264 稍后通过向 compile()
添加可选参数以灵活的方式解决了这个问题。
无论如何,出现在“顶部附近”(请参阅上面的语法)由 exec
、execfile()
或 compile()
动态编译的文本中的 future_statement 将应用于生成的代码块,但对执行此类 exec
、execfile()
或 compile()
的模块没有进一步的影响。但是,这不能用于影响 eval()
或 input()
,因为它们只允许表达式输入,而 future_statement 不是表达式。
已解决的问题:原生交互式 Shell
获得交互式 Shell 有两种方法
- 从命令行调用 Python 而不带脚本参数。
- 从命令行调用 Python,使用
-i
开关并带脚本参数。
交互式 Shell 可以看作是运行时编译的极端情况(见上文):实际上,在交互式 Shell 提示符下键入的每个语句都会运行 exec
、compile()
或 execfile()
的新实例。在交互式 Shell 中键入的 future_statement 将应用于 Shell 会话的剩余生命周期,就像 future_statement 出现在模块的顶部一样。
已解决的问题:模拟交互式 Shell
“手工构建”的交互式 Shell(由 IDLE 和 Emacs Python 模式等工具构建)应该表现得像原生交互式 Shell(见上文)。但是,原生交互式 Shell 内部使用的机制尚未公开,并且工具构建自己的交互式 Shell 以实现所需行为的方法并不明确。
注意:PEP 264 稍后解决了这个问题,通过向标准 codeop.py
添加智能。不使用标准库 Shell 帮助器的模拟 Shell 可以通过利用 PEP 264 添加的 compile()
的新可选参数来获得类似的效果。
问答
如何使用“from __past__”版本来获取旧的行为?
不在本 PEP 的范围内。作者认为不太可能。如果您想继续研究,请编写一个 PEP。
Python 虚拟机变化导致的不兼容性怎么办?
不在本 PEP 的范围内,尽管 PEP 5 也建议在那里有一个宽限期,并且 future_statement 也可能在那里发挥作用。
Python 的 C API 变化导致的不兼容性怎么办?
不在本 PEP 的范围内。
我想将 future_statements 包裹在 try/except 块中,以便根据我正在运行的 Python 版本使用不同的代码。为什么我不能这样做?
抱歉!try/except
是一个运行时特性;future_statements 主要是编译时的小技巧,并且你的 try/except
发生在编译器完成很久之后。也就是说,当你执行 try/except
时,模块中生效的语义已经确定了。由于 try/except
无法实现它看起来应该实现的功能,因此它是不允许的。我们也希望使这些特殊语句非常容易查找和识别。
请注意,你可以直接导入 __future__
,并使用其中的信息以及 sys.version_info
来确定你正在运行的版本相对于给定特性的状态。
回到 nested_scopes 示例,如果 2.2 版本发布了,而我还没有更改我的代码,那么如何保留 2.1 版本的行为?
通过继续使用 2.1,并且在修改代码之前不要迁移到 2.2。future_statement 的目的是为了方便那些及时更新最新版本的人。如果你不更新,我们并不讨厌你,但你的问题将更难解决,并且遇到这些问题的人需要编写一个 PEP 来解决它们。future_statement 针对的是不同的受众。
重载 import
很糟糕。为什么不为此引入一个新的语句?
比如 lambda lambda nested_scopes
?也就是说,除非我们引入一个新的关键字,否则我们无法引入一个全新的语句。但如果我们引入一个新的关键字,这本身就会破坏旧代码。这将是太讽刺了。是的,重载 import
确实很糟糕,但没有其他方案那么糟糕——就目前而言,future_statements 是 100% 向后兼容的。
版权
本文档已置于公共领域。
参考文献和脚注
来源:https://github.com/python/peps/blob/main/peps/pep-0236.rst
最后修改时间:2023-09-09 17:39:29 GMT