Python 何时完成元编程

When is meta-programming done in Python

我读过一篇论文,似乎说 Lisp、Scheme 和类似 Lisp 的宏的元编程发生在编译时:http://tratt.net/laurie/research/pubs/html/tratt__compile-time_meta-programming_in_a_dynamically_typed_oo_language/

它似乎还表明 Python 等动态语言并没有使用很多编译时元编程。我知道 Java 可以使用 class 加载程序进行编译时元编程。在使用 Metaclasses 和装饰器的 Python 元编程以及使用 type()、isInstance() 等方法的反射中...这都是运行时的元编程还是背后还有更多?

简而言之就是:是的,装饰器、metaclasses等主要是运行时间发生的事情。

这也意味着在理解 Python.1

中,SmallTalk 通常是比 Lisp 更好的参考点

长版有点牵强

Python 中的

"Compile time" 是关于将函数定义体、class 定义和模块本身编译为字节码。 "Runtime" 涉及解释这些机构。2

特别是,defclass 语句(编译为)运行像其他任何代码一样执行的时间代码。


例如,考虑这个模块:

@spam
def eggs():
    print(3)

print(3) 主体被编译成一些字节码,执行时会查找 print 并使用参数 3 调用它。然后可以将该字节码视为常量。

然后模块主体被编译成类似这样的伪代码:

eggs = spam(FunctionType('eggs', eggs_bytecode_constant, (), other_stuff))

当您 import 模块(或 运行 作为脚本)时,编译后的代码会在 运行 时执行。所以,这就是装饰器被调用的时候。


同样,考虑一下:

class Spam(metaclass=MetaSpam):
    def eggs(self):
        pass

首先,pass 被编译成字节码,除了 return None 之外什么都不做,可以将其视为常量。

接下来,class 主体被编译成字节码。由于 class 正文只有 def 语句,字节码相当于:

eggs = FunctionType('eggs', eggs_bytecode_constant, ('self',), other_stuff))

接下来,class 语句被编译成字节码,执行如下操作:

_namespace = {}
exec(Spam_bytecode_constant, _namespace)
Spam = MetaSpam('Spam', (object,), _namespace)

然后,当您 import 模块(或 运行 作为脚本)时,该字节码将被执行。所以,这就是调用 metaclass 的时候,创建了 class 对象。


这意味着你几乎可以忽略编译时发生的问题。3

如果您想直接调用 type(或自定义元 class),您将获得与 class 语句完全相同的效果。您甚至可以手动从字节码对象构造函数对象,并且您获得与 def 语句或 lambda 表达式完全相同的效果。您可以在创建函数或 class 之后修改它——例如,通过 Spam.cheese = cheese 将新方法添加到 class 最终与直接在 class语句。4

这也意味着反射在Python中并不神奇。对象在 public 属性中携带它们的类型信息,inspect 模块所做的事情与解释器使用相同属性所做的事情几乎相同。

但是,另一方面,这意味着一些用 Lisp 宏很容易做到的事情——比如将表达式的 AST 而不是表达式的值作为参数——用 Python 元编程。


好吧,我说这是不可能的,但是......如果你想在 Python 中进行 Lisp 风格的元编程,你实际上也可以做到。它只是意味着编写和安装 import hooks.5

通常,在 Python 中,import 找到源文件,使用 bytes.decode 将其解码为文本,使用 tokenize 模块对其进行标记,解析标记使用 ast.parse,并使用 compile 编译结果。所有这些部分都暴露给 Python 代码,并且(在 3.4+ 中)整个导入系统本身是用 Python 编写的,使用您可以自己使用的相同模块。

因此,导入挂钩可以安装自定义加载器,例如,像默认加载器一样解码、标记化和解析,然后像 Lisp 风格的宏一样修改 AST,然后编译和 return 结果类似于默认加载器。

如果您完全有兴趣这样做,您应该看看 MacroPy


1.事实上,IIRC、Forman 的第一版和 Danforth 的 SmallTalk 书 Putting Metaclasses to Work 以及 Danforth 的另一篇论文对 Python' 产生了主要影响元编程设计。

2。在交互模式下,Python 编译然后一次执行一个语句,有点混合,但思想并没有太大不同。

3.事实上,不同的实现可以选择在编译时比 CPython 多做或少做,只要语义最终相同。

4.除了一些细微的问题,例如 def 语句在上面的 _namespace 内部与 globals() 内部执行的方式,这可能会影响 super().

5.原始 PEP 很好地涵盖了历史和基本原理,但没有涵盖现代 Python 中编写和安装挂钩的方式。为此,请阅读 the import system 上的参考文档并点击指向 importlib 包的链接。