我读过一篇文章,似乎说Lisp、Scheme和类似Lisp的宏的元编程发生在编译时:http://tratt.net/laurie/research/pubs/html/tratt__compile-time_meta-programming_in_a_dynamically_typed_oo_language/。在
它似乎还指出,像Python这样的动态语言不使用太多的编译时元编程。我知道在某种程度上,Java可以使用类加载器进行编译时元编程。在使用元类和修饰符的Python元编程中,以及使用诸如type()、isInstance()等方法进行反射的Python元编程中……这都是运行时的元编程,还是背后还有更多的元编程?在
简短的说法是:是的,decorator、元类等主要是在运行时发生的事情。在
这也意味着,对于理解Python中的元编程,SmallTalk通常比Lisp更好
长版本有点复杂。在
Python中的“编译时”是指将函数定义、类定义和模块本身编译为字节码“运行时”包括解释这些实体。2
特别是,像
def
和class
语句这样的语句是(编译为)运行时代码的,它们可以像其他语句一样执行。在例如,考虑以下模块:
print(3)
主体被编译成一些字节码,在执行时,它查找print
,并用参数3
调用它。然后可以将字节码视为常量。在然后模块主体被编译成类似这样的伪代码:
^{pr2}$当您
import
该模块(或将其作为脚本运行)时,编译的代码将在运行时执行。所以,这时就有人打电话给装饰工了。在同样,考虑一下:
首先,
pass
被编译成字节码,它只返回None
,它可以被视为常量。在接下来,这个类主体被编译成字节码。由于类主体只有
def
语句,因此字节码的作用与此等效:接下来,该
class
语句被编译成字节码,执行如下操作:然后,当您
import
该模块(或将其作为脚本运行)时,该字节码将被执行。因此,这时调用元类,创建类对象。在这意味着您几乎可以忽略编译时发生的事情。3
如果您想直接调用
type
(或自定义元类),则会获得与class
语句完全相同的效果。您甚至可以手动从字节码对象中构造函数对象,并获得与def
语句或lambda
表达式完全相同的效果。而且,您可以在函数或类被创建后对其进行修改,例如,通过Spam.cheese = cheese
向类添加新方法与直接在class
语句中定义它们没有什么不同。4这也意味着在Python中反射并不是什么神奇的东西。对象在public属性中携带它们的类型信息,^{} 模块所做的工作与解释器对相同属性所做的工作几乎相同。在
但是,另一方面,这意味着一些用Lisp宏容易做的事情,比如将表达式的AST而不是表达式的值作为参数,在Python元编程中是不可能的。在
好吧,我说这是不可能的,但是……如果你想用Python做Lisp风格的元编程,实际上你也可以做到。它只意味着编写和安装import hooks。5
通常,在Python中,
import
查找源文件,用bytes.decode
将其解码为文本,用tokenize
模块标记它,用ast.parse
解析标记,并用compile
编译结果。所有这些部分都暴露在Python代码中,而且(在3.4以上版本中)整个导入系统本身都是用Python编写的,使用的模块与您自己可以使用的相同。在因此,导入钩子可以安装一个自定义加载程序,该加载程序将像默认加载程序一样进行解码、标记化和解析,然后像Lisp样式的宏那样修改AST,然后像默认加载程序一样编译并返回结果。在
如果您对此感兴趣,应该查看MacroPy。在
1。事实上,IIRC、Forman的第一版和Danforth的SmallTalk书让元类工作以及Danforth的另一篇论文是对Python元程序的主要影响设计。
2。在交互模式下,Python一次编译并执行一个语句,将一些东西混合在一起,但思想没有太大不同。
3。实际上,不同的实现可以选择在编译时比CPython做更多或更少的工作,只要语义最终相同。
4。除了一些微妙的问题,例如}内部执行的方式不同,这可能会影响
def
语句在上面的那个_namespace
内与{super()
。5。最初的PEP很好地涵盖了历史和基本原理,但没有涵盖在现代Python中编写和安装钩子的方法。为此,请阅读the import system上的参考文档,并按照
importlib
包的链接进行操作。相关问题 更多 >
编程相关推荐