Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

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 转换器轻松实现。预处理器具有各种不同的用途。

一些示例

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* 还将具有以下属性

  • interactivebool):如果处于交互模式,则为 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_tagstr):优化标签。默认优化标签为 '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_tagopt,并且 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 转换器非常容易。

讨论

现有技术

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