PEP 511 – 代码转换器 API
- 作者:
- Victor Stinner <vstinner at python.org>
- 状态:
- 已拒绝
- 类型:
- 标准轨迹
- 创建:
- 2016年1月4日
- Python 版本:
- 3.6
拒绝通知
此 PEP 已被其作者拒绝。
此 PEP 被视为对类似 Python 但与常规 Python 语言不兼容的新型 Python 式编程语言的认可。最终决定不推广与 Python 不兼容的语法。
此 PEP 也被视为一个很好的实验新 Python 特性的工具,但无需此 PEP 也可以进行实验,只需使用 importlib hook 即可。如果某个特性变得有用,它应该直接成为 Python 的一部分,而不是依赖于第三方 Python 模块。
最后,此 PEP 的驱动力是 FAT Python 优化项目,该项目已于 2016 年放弃,因为它无法展现任何明显的加速,也因为缺乏时间来实现最先进和复杂的优化。
摘要
建议一个 API 来注册字节码和 AST 转换器。还添加 -o OPTIM_TAG
命令行选项来更改 .pyc
文件名,-o noopt
禁用窥孔优化器。如果缺少 .pyc
文件且转换代码所需的代码转换器也缺失,则在导入时引发 ImportError
异常。如果代码已提前转换(从 .pyc
文件加载),则不需要代码转换器。
基本原理
Python 没有提供转换代码的标准方法。转换代码的项目使用了各种 hook。MacroPy 项目使用导入 hook:它在 sys.meta_path
中添加了自己的模块查找器来挂钩其 AST 转换器。另一种选择是修补内置的 compile()
函数。还有更多方法来挂钩代码转换器。
Python 3.4 在 importlib.abc.SourceLoader
中添加了一个 compile_source()
方法。但是代码转换的范围比仅仅导入模块要广,请参见下面描述的用例。
编写优化器或预处理器不在此 PEP 的范围内。
用法 1:AST 优化器
转换抽象语法树 (AST) 是实现优化器的一种便捷方法。在 AST 上操作比在字节码上操作更容易,AST 包含更多信息并且更高级别。
由于优化可以在提前完成,因此可以实现复杂但缓慢的优化。
可以使用 AST 优化器实现的优化示例
使用保护(参见 PEP 510),可以实现更多种类的优化。示例
- 简化可迭代对象:当用作可迭代对象时,将
range(3)
替换为(0, 1, 2)
- 循环展开
- 调用纯内置函数:将
len("abc")
替换为3
- 将已使用的内置符号复制到常量
- 另请参见 fatoptimizer 中实现的优化,这是一个针对 Python 3.6 的静态优化器。
以下问题可以使用 AST 优化器来实现
用法 2:预处理器
预处理器可以使用 AST 转换器轻松实现。预处理器具有各种不同的用途。
一些示例
- 删除调试代码(如断言和日志),以加快代码的运行速度以用于生产环境。
- 尾调用优化
- 添加性能分析代码
- 惰性求值:参见 lazy_python(字节码转换器)和 MacroPy 的惰性宏(AST 转换器)
- 将字典字面量更改为 collection.OrderedDict 实例
- 声明常量:参见 @asconstants of codetransformer
- 特定领域语言 (DSL),如 SQL 查询。Python 语言本身不需要修改。之前尝试为 SQL 实现 DSL(如 PEP 335 - 可重载布尔运算符)已被拒绝。
- 函数式语言的模式匹配
- 字符串插值,但 PEP 498 已合并到 Python 3.6 中。
MacroPy 有一个很长的示例和用例列表。
此 PEP 没有添加任何新的代码转换器。使用代码转换器需要一个外部模块并手动注册它。
另请参见 PyXfuscator:Python 混淆器、反混淆器和用户辅助的反编译器。
用法 3:禁用所有优化
Ned Batchelder 要求添加一个选项来禁用窥孔优化器,因为它使代码覆盖率更难以实现。请参阅 python-ideas 邮件列表上的讨论:禁用所有窥孔优化。
此 PEP 添加了一个新的 -o noopt
命令行选项来禁用窥孔优化器。在 Python 中,这很简单
sys.set_code_transformers([])
它将修复 问题 #2506:添加禁用优化的机制。
用法 4:用 Python 编写新的字节码优化器
Python 3.6 使用窥孔优化器优化代码。根据定义,窥孔优化器对代码的视野很窄,因此只能实现基本的优化。优化器重写字节码。很难增强它,因为它是用 C 编写的。
使用此 PEP,可以完全用 Python 实现新的字节码优化器并实验新的优化。
一些优化在 AST 上更容易实现,例如常量折叠,但字节码上的优化仍然有用。例如,当 AST 编译成字节码时,可能会发出无用的跳转,因为编译器很天真,没有尝试优化任何内容。
用例
本节提供用例示例,说明何时以及如何使用代码转换器。
交互式解释器
可以使用代码转换器与 Python 中很流行且通常用于演示 Python 的交互式解释器。
代码在运行时转换,因此当使用代价高昂的代码转换器时,解释器可能会变慢。
构建转换后的包
可以构建转换后的代码包。
转换器可以具有配置。配置未存储在包中。
包的所有 .pyc
文件必须使用相同的代码转换器和相同的转换器配置进行转换。
可以使用不同的优化器标签构建不同的 .pyc
文件。示例:fat
用于默认配置,fat_inline
用于启用了函数内联的不同配置。
包可以包含具有不同优化器标签的 .pyc
文件。
安装包含转换后的 .pyc 文件的包
可以安装包含转换后的 .pyc
文件的包。
安装包中包含的所有具有任何优化器标签的 .pyc
文件,而不仅仅是当前优化器标签的 .pyc
文件。
安装包时构建 .pyc 文件
如果包不包含当前优化器标签的任何 .pyc
文件(或某些 .pyc
文件缺失),则在安装过程中创建 .pyc
文件。
需要优化器标签的代码转换器。否则,安装将失败并出现错误。
执行转换后的代码
可以执行转换后的代码。
如果缺少当前优化器标签的 .pyc
文件且转换代码所需的代码转换器也缺失,则在导入时引发 ImportError
异常。
这里有趣的一点是,如果所有必需的 .pyc
文件都已可用,则不需要代码转换器来执行转换后的代码。
代码转换器 API
代码转换器是一个具有 ast_transformer()
和/或 code_transformer()
方法(下面描述的 API)以及 name
属性的类。
为了提高效率,如果 code_transformer()
或 ast_transformer()
方法什么也不做,请不要定义它们。
name
属性(str
)必须是一个短字符串,用于标识优化器。它用于构建 .pyc
文件名。名称中不能包含点('.'
)、连字符('-'
)或目录分隔符:点用于分隔 .pyc
文件名中的字段,连字符用于连接代码转换器名称以构建优化器标签。
注意
在使用 AST 转换器转换导入时的模块时,在 *context* 中传递模块的完全限定名称会很好,但看起来在 PyParser_ASTFromStringObject()
中无法获取此信息。
code_transformer() 方法
原型
def code_transformer(self, code, context):
...
new_code = ...
...
return new_code
参数
- code:代码对象
- context:一个具有 *optimize* 属性(
int
)的对象,表示优化级别(0、1 或 2)。*optimize* 属性的值来自compile()
函数的 *optimize* 参数,默认情况下等于sys.flags.optimize
。
每个 Python 实现都可以向 *context* 添加额外的属性。例如,在 CPython 中,*context* 还将具有以下属性
- interactive(
bool
):如果处于交互模式,则为 true
XXX 添加更多标志?
XXX 将标志 int 替换为子命名空间或特定属性?
该方法必须返回一个代码对象。
代码转换器在编译为字节码后运行
ast_transformer() 方法
原型
def ast_transformer(self, tree, context):
...
return tree
参数
- tree:一个 AST 树
- context:一个具有
filename
属性(str
)的对象
它必须返回一个 AST 树。它可以就地修改 AST 树,或创建一个新的 AST 树。
AST 转换器在解析器创建 AST 之后以及编译为字节码之前被调用。将来可能会向 *context* 添加新的属性。
更改
简而言之,添加
- -o OPTIM_TAG 命令行选项
- sys.implementation.optim_tag
- sys.get_code_transformers()
- sys.set_code_transformers(transformers)
- ast.PyCF_TRANSFORMED_AST
获取/设置代码转换器的 API
添加新的函数来注册代码转换器
sys.set_code_transformers(transformers)
:设置代码转换器列表并更新sys.implementation.optim_tag
sys.get_code_transformers()
:获取代码转换器列表。
代码转换器的顺序很重要。运行转换器 A,然后运行转换器 B,与运行转换器 B,然后运行转换器 A 可能得到不同的输出。
预置新代码转换器的示例
transformers = sys.get_code_transformers()
transformers.insert(0, new_cool_transformer)
sys.set_code_transformers(transformers)
所有 AST 转换器都按顺序运行(例如,第二个转换器获取第一个转换器的输入),然后所有字节码转换器都按顺序运行。
优化器标签
更改
- 添加
sys.implementation.optim_tag
(str
):优化标签。默认优化标签为'opt'
。 - 添加一个新的
-o OPTIM_TAG
命令行选项来设置sys.implementation.optim_tag
。
对 importlib
的更改
importlib
使用sys.implementation.optim_tag
来构建导入模块的.pyc
文件名,而不是始终使用opt
。还删除了优化级别0
使用默认优化器标签'opt'
的特殊情况,以简化代码。- 加载模块时,如果
.pyc
文件丢失但.py
文件可用,则仅当代码优化器具有与当前标签相同的优化器标签时才使用.py
,否则会引发ImportError
异常。
用于确定是否可以编译 .py
文件以导入模块的 use_py()
函数的伪代码
def transformers_tag():
transformers = sys.get_code_transformers()
if not transformers:
return 'noopt'
return '-'.join(transformer.name
for transformer in transformers)
def use_py():
return (transformers_tag() == sys.implementation.optim_tag)
sys.get_code_transformers()
的顺序很重要。例如,fat
转换器后跟 pythran
转换器会生成优化器标签 fat-pythran
。
使用默认优化器标签('opt'
)时,importlib
模块的行为保持不变。
窥孔优化器
默认情况下,sys.implementation.optim_tag
为 opt
,并且 sys.get_code_transformers()
返回一个包含一个代码转换器的列表:窥孔优化器(优化字节码)。
使用 -o noopt
禁用窥孔优化器。在这种情况下,优化器标签为 noopt
,并且没有注册代码转换器。
使用 -o opt
选项无效。
AST 增强
简化 AST 转换器实现的增强功能
- 添加一个新的编译器标志
PyCF_TRANSFORMED_AST
以获取转换后的 AST。PyCF_ONLY_AST
返回转换器之前的 AST。
示例
.pyc 文件名
os
模块的 .pyc
文件名的示例。
使用默认优化器标签 'opt'
.pyc 文件名 | 优化级别 |
---|---|
os.cpython-36.opt-0.pyc |
0 |
os.cpython-36.opt-1.pyc |
1 |
os.cpython-36.opt-2.pyc |
2 |
使用 'fat'
优化器标签
.pyc 文件名 | 优化级别 |
---|---|
os.cpython-36.fat-0.pyc |
0 |
os.cpython-36.fat-1.pyc |
1 |
os.cpython-36.fat-2.pyc |
2 |
字节码转换器
将所有字符串替换为 "Ni! Ni! Ni!"
的可怕字节码转换器
import sys
import types
class BytecodeTransformer:
name = "knights_who_say_ni"
def code_transformer(self, code, context):
consts = ['Ni! Ni! Ni!' if isinstance(const, str) else const
for const in code.co_consts]
return types.CodeType(code.co_argcount,
code.co_kwonlyargcount,
code.co_nlocals,
code.co_stacksize,
code.co_flags,
code.co_code,
tuple(consts),
code.co_names,
code.co_varnames,
code.co_filename,
code.co_name,
code.co_firstlineno,
code.co_lnotab,
code.co_freevars,
code.co_cellvars)
# replace existing code transformers with the new bytecode transformer
sys.set_code_transformers([BytecodeTransformer()])
# execute code which will be transformed by code_transformer()
exec("print('Hello World!')")
输出
Ni! Ni! Ni!
AST 转换器
与字节码转换器示例类似,AST 转换器也将所有字符串替换为 "Ni! Ni! Ni!"
import ast
import sys
class KnightsWhoSayNi(ast.NodeTransformer):
def visit_Str(self, node):
node.s = 'Ni! Ni! Ni!'
return node
class ASTTransformer:
name = "knights_who_say_ni"
def __init__(self):
self.transformer = KnightsWhoSayNi()
def ast_transformer(self, tree, context):
self.transformer.visit(tree)
return tree
# replace existing code transformers with the new AST transformer
sys.set_code_transformers([ASTTransformer()])
# execute code which will be transformed by ast_transformer()
exec("print('Hello World!')")
输出
Ni! Ni! Ni!
其他 Python 实现
所有 Python 实现都应该实现 PEP 511,但字节码和 AST 并没有标准化。
顺便说一句,即使在 CPython 的次要版本之间,AST API 也会发生变化。存在差异,但只是细微的差异。例如,编写一个可以在 Python 2.7 和 Python 3.5 上运行的 AST 转换器非常容易。
讨论
- [Python-ideas] PEP 511: API for code transformers(2016 年 1 月)
- [Python-Dev] AST optimizer implemented in Python(2012 年 8 月)
现有技术
AST 优化器
问题 #17515 “Add sys.setasthook() to allow to use a custom AST” optimizer 是代码转换器 API 的首次尝试,但特定于 AST。
2015 年,Victor Stinner 编写了 fatoptimizer 项目,这是一个专门用于使用保护的函数的 AST 优化器。
2014 年,Kevin Conway 创建了 PyCC 优化器。
2012 年,Victor Stinner 编写了 astoptimizer 项目,这是一个实现各种优化的 AST 优化器。大多数有趣的优化会破坏 Python 语义,因为在某些内容发生变化时没有使用保护来禁用优化。
2011 年,Eugene Toder 提出在新的 AST 优化器中重写一些窥孔优化:问题 #11549,Build-out an AST optimizer, moving some functionality out of the peephole optimizer。该补丁添加了 ast.Lit
(建议将其重命名为 ast.Literal
)。
Python 预处理器
- MacroPy:MacroPy 是 Python 编程语言中语法宏的实现。MacroPy 提供了一种机制,允许用户定义的函数(宏)在导入时对 Python 程序的抽象语法树 (AST) 执行转换。
- pypreprocessor:Python 中的 C 样式预处理器指令,例如
#define
和#ifdef
字节码转换器
- codetransformer:受
ast
模块的NodeTransformer
启发的 CPython 字节码转换器。 - byteplay:Byteplay 允许您将 Python 代码对象转换为等效的对象,这些对象易于操作,并允许您将这些对象转换回活动的 Python 代码对象。它可用于对 Python 函数应用疯狂的转换,也可用于学习 Python 字节码的复杂性。请参阅 byteplay 文档。
另请参阅
版权
本文档已归入公有领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0511.rst
上次修改时间:2023-09-09 17:39:29 GMT