模块导入时和运行时比较 Python程序员会区分“导入时”和“运行时”,不过这两个术语没有严格的定义,而且二者之间存在着灰色地带。
在导入时,解释器会从上到下一次性解析完 .py 模块的源码,然后生成用于执行的字节码。如果句法有错误,就在此时报告。
如果本地的 __pycache__ 文件夹中有最新的 .pyc 文件,解释器会跳过上述步骤,因为已经有运行所需的字节码了。
编译肯定是导入时的活动,不过那个时期还会做些其他事,因为Python中的语句几乎都是可执行的,也就是说语句可能会运行用户代码,修改用户程序的状态。
尤其是 import 语句,它不只是声明,在进程中首次导入模块时,还会运行所导入模块中的全部顶层代码——以后导入相同的模块则使用缓存,只做名称绑定。那些顶层代码可以做任何事,包括通常在“运行时”做的事,例如连接数据库。
因此,“导入时”与“运行时”之间的界线是模糊的:import语句可以触发任何“运行时”行为。
导入时会“运行全部顶层代码”,但是“顶层代码”会经过一些加工。导入模块时,解释器会执行顶层的def语句,可是这么做有什么作用呢?
解释器会编译函数的定义体(首次导入模块时),把函数对象绑定到对应的全局名称上,但是显然解释器不会执行函数的定义体。通常这意味着解释器在导入时定义顶层函数,但是仅当在运行时调用函数时才会执行函数的定义体。
对类来说,情况就不同了:在导入时,解释器会执行每个类的定义体,甚至会执行嵌套类的定义体。执行类定义体的结果是,定义了类的属性和方法,并构建了类对象。从这个意义上理解,类的定义体属于“顶层代码”,因为它在导入时运行。
举例:理解代码执行顺序的练习 强烈建议在代码中添加断点,debug一步一步地查看执行顺序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 print ('<[100]> evalsupport module start' )def deco_alpha (cls ): print ('<[200]> deco_alpha' ) def inner_1 (self ): print ('<[300]> deco_alpha:inner_1' ) cls.method_y = inner_1 return cls class MetaAleph (type ): print ('<[400]> MetaAleph body' ) def __init__ (cls, name, bases, dic ): print ('<[500]> MetaAleph.__init__' ) def inner_2 (self ): print ('<[600]> MetaAleph.__init__:inner_2' ) cls.method_z = inner_2 print ('<[700]> evalsupport module end' )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 from evalsupport import deco_alphaprint ('<[1]> evaltime module start' )class ClassOne (): print ('<[2]> ClassOne body' ) def __init__ (self ): print ('<[3]> ClassOne.__init__' ) def __del__ (self ): print ('<[4]> ClassOne.__del__' ) def method_x (self ): print ('<[5]> ClassOne.method_x' ) class ClassTwo (object ): print ('<[6]> ClassTwo body' ) @deco_alpha class ClassThree (): print ('<[7]> ClassThree body' ) def method_y (self ): print ('<[8]> ClassThree.method_y' ) class ClassFour (ClassThree ): print ('<[9]> ClassFour body' ) def method_y (self ): print ('<[10]> ClassFour.method_y' ) if __name__ == '__main__' : print ('<[11]> ClassOne tests' , 30 * '.' ) one = ClassOne() one.method_x() print ('<[12]> ClassThree tests' , 30 * '.' ) three = ClassThree() three.method_y() print ('<[13]> ClassFour tests' , 30 * '.' ) four = ClassFour() four.method_y() print ('<[14]> evaltime module end' )
场景1 :被当做模块导入到其他模块中 evaltime.py 被当做模块导入到其他模块中
1 2 3 4 5 6 7 8 9 10 <[100 ]> evalsupport module start <[400 ]> MetaAleph body <[700 ]> evalsupport module end <[1 ]> evaltime module start <[2 ]> ClassOne body <[6 ]> ClassTwo body <[7 ]> ClassThree body <[200 ]> deco_alpha <[9 ]> ClassFour body <[14 ]> evaltime module end
导入evaltime模块时,其中的 evalsupport 模块中的所有顶层代码在导入模块时运行;解释器会编译 deco_alpha 函数,但是不会执行定义体;ClassThree 被装饰器 deco_alpha 修饰,先计算被装饰的类 ClassThree 的定义体,然后运行装饰器函数deco_alpha ;evaltime模块是导入的,因此不会运行 if __name__=='__main__': 块。
小结:
这个场景由简单的 import evaltime 语句触发;
解释器会执行所导入模块及其依赖(evalsupport)中的每个类定义体;
解释器先计算类的定义体,然后调用依附在类上的装饰器函数,这是合理的行为,因为必须先构建类对象,装饰器才有类对象可处理;
在这个场景中,只运行了一个用户定义的函数或方法——deco_alpha装饰器。
场景2 :直接运行模块 运行evaltime.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <[100 ]> evalsupport module start <[400 ]> MetaAleph body <[700 ]> evalsupport module end <[1 ]> evaltime module start <[2 ]> ClassOne body <[6 ]> ClassTwo body <[7 ]> ClassThree body <[200 ]> deco_alpha <[9 ]> ClassFour body <[11 ]> ClassOne tests .............................. <[3 ]> ClassOne.__init__ <[5 ]> ClassOne.method_x <[12 ]> ClassThree tests .............................. <[300 ]> deco_alpha:inner_1 <[13 ]> ClassFour tests .............................. <[10 ]> ClassFour.method_y <[14 ]> evaltime module end <[4 ]> ClassOne.__del__
deco_alpha 装饰器修改了 ClassThree.method_y 方法,因此调用 three.method_y()时会运行 inner_1 函数的定义体。只有程序结束时,绑定在全局变量 one 上的 ClassOne 实例才会被垃圾回收程序回收。
场景2主要想说明的是,类装饰器可能对子类没有影响。在示例中,把 ClassFour 定义为 ClassThree 的子类,ClassThree 类上依附的 @deco_alpha 装饰器把 method_y 方法替换掉了,但是这对 ClassFour 类根本没有影响。当然,如果 ClassFour.method_y 方法使用 super(...) 调用 ClassThree.method_y 方法,我们便会看到装饰器起作用,执行inner_1函数。
1 2 3 4 5 6 class ClassFour (ClassThree ): print ('<[9]> ClassFour body' ) def method_y (self ): print ('<[10]> ClassFour.method_y' ) super (ClassFour, self ).method_y()
1 2 3 <[13 ]> ClassFour tests .............................. <[10 ]> ClassFour.method_y <[300 ]> deco_alpha:inner_1