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 不兼容的语法。

此 PEP 也被视为尝试新 Python 功能的有用工具,但无需此 PEP,仅通过 importlib 钩子即可进行尝试。如果某个功能变得有用,它应该直接成为 Python 的一部分,而不是依赖于第三方 Python 模块。

最后,此 PEP 是由 FAT Python 优化项目驱动的,该项目于 2016 年被放弃,因为它无法显示任何显著的速度提升,而且由于时间不足而无法实现最先进和最复杂的优化。

摘要

提出一个 API 来注册字节码和 AST 转换器。另外添加 -o OPTIM_TAG 命令行选项来更改 .pyc 文件名,-o noopt 会禁用窥孔优化器。如果在导入时 .pyc 文件丢失且转换代码所需的代码转换器也丢失,则引发 ImportError 异常。代码转换器不需要在代码被转换(从 .pyc 文件加载)之前就可用。

基本原理

Python 没有提供标准的转换代码的方法。转换代码的项目使用各种钩子。MacroPy 项目使用导入钩子:它将其自己的模块查找器添加到 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 文件丢失且转换代码所需的代码转换器也丢失,则引发 ImportError 异常。

这里有趣的一点是,如果所有必需的 .pyc 文件都已可用,则执行已转换代码不需要代码转换器。

代码转换器 API

代码转换器是一个具有 ast_transformer() 和/或 code_transformer() 方法(API 描述如下)和一个 name 属性的类。

为了效率,如果 code_transformer()ast_transformer() 方法不做任何事情,请不要定义它们。

name 属性(str)必须是一个用于标识优化器的短字符串。它用于构建 .pyc 文件名。名称不得包含点('.')、连字符('-')或目录分隔符:点用于分隔 .pyc 文件名中的字段,连字符用于连接代码转换器名称以构建优化器标签。

注意

在从模块导入时使用 AST 转换器转换模块时,将模块的完全限定名称传递给上下文会很有用,但 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_tagoptsys.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 “添加 sys.setasthook() 以允许使用自定义 AST”优化器是代码转换器 API 的首次尝试,但仅限于 AST。

2015 年,Victor Stinner 编写了 fatoptimizer 项目,这是一个使用守卫专门化函数的 AST 优化器。

2014 年,Kevin Conway 创建了 PyCC 优化器。

2012 年,Victor Stinner 编写了 astoptimizer 项目,这是一个实现各种优化的 AST 优化器。大多数有趣的优化会破坏 Python 的语义,因为没有守卫来禁用在某些东西改变时的优化。

2011 年,Eugene Toder 提议在新的 AST 优化器中重写一些窥孔优化:问题 #11549,构建 AST 优化器,将部分功能移出窥孔优化器。补丁添加了 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

最后修改:2025-02-01 08:59:27 GMT