lazyload模块,可以实现模块的惰性加载。所谓惰性加载,是指在使用import引入模块时,并不真正的引入,真正的引入发生获取模块的某个属性时。
项目地址: https://github.com/thomasballinger/lazyload, 下面是作者提供的测试代码
import lazyload
lazyload.make_lazy('requests')
import requests # nearly instant
requests.get # takes 1.2 seconds
make_lazy方法将requests模块设置为惰性加载模块,因此下一行代码import requests 并不会真正的引入该模块,最后一行代码尝试方法这个模块的get函数,此时,才会真正的引入该模块。
让模块具备惰性加载的特性,可以避免程序在启动时,因大量的import操作导致启动时间过长,模块导入的时间并没有减少,只是被分散到了获取模块某个具体属性的操作上。在生产环境中是否有实践意义,我暂时难以评估。
该模块目前只支持python 2.7, 3.4, 3.5 这3个版本,这是其实现原理决定的。虽然不支持高版本,但阅读其源码,仍然可以帮助你理解python在模块引入上的一些细节。
make_lazy函数是模块的核心,结合测试代码,make_lazy('requests')将requests模块具备惰性加载的特性,在import requests 语句执行时,真正引入的,并不是requests,而是make_lazy内部定义的LazyModule类的实例。
以下两行代码是实现这一效果的关键
sys_modules = sys.modules # 29行
sys_modules[module_path] = LazyModule() # 61行
一个模块被import语句引入时,首先会检查是否已经存在于sys.modules中,如果存在,则直接从sys.modules获取,不存在,则引入模块并存储在sys.modules中。
sys.modules是一个字典结构,key是模块的名称,value是模块。
make_lazy('requests') 的执行先于 import requests,如此一来,sys_modules中便存在了一个key为requests,value为LazyModule实例的key-value对。那么当import requests 被执行时,实际引入的是LazyModule实例,而非真正的requests模块。
make_lazy函数创建一个LazyModule实例对象,并通过sys.modules将这个实例伪装成用户想要导入的模块。
当requests.get被执行时,LazyModule实例的__getattribute__方法将被执行,这并不难理解,这种object.attribute的写法,实际调用的正是这种object的__getattribute__方法。
让我们来看一下__getattribute__ 方法
def __getattribute__(self, attr):
if module.value is None:
del sys_modules[module_path]
module.value = __import__(module_path)
sys_modules[module_path] = __import__(module_path)
return getattr(module.value, attr)
module对象是在make_lazy 函数的32行被创建的: module = NonLocal(None), 当requests.get被执行时,module.value 一定为None, 在if语句块里,做了3个操作
最后一步,使用getattr函数返回get函数: return getattr(module.value, attr)
我并没有真正理解__mro__ 方法在LazyModule类里的作用,不过没关系,我们阅读源码的目的,不是完全彻底的搞懂源码里的每一行代码,而是要通过阅读优秀的源码,来丰富自己的知识,提高自己的水平。虽然不理解在源码里的作用,我们却可以借此机会学习它的用法。
__mro__ 方法的作用,是决定多继承情形下方法解析的顺序。
class A():
def foo(self):
print('a')
class B():
def foo(self):
print('b')
class C(A, B):
pass
c = C()
c.foo()
C继承了A和B,A和B都实现了foo方法,那么c.foo()在执行时,究竟是调用A的foo方法还是B的foo方法呢? 大概率你会去猜调用A的foo,虽然结论是对的,但猜总归是猜,我们还是要从根本上解释它。
多继承情况下,不论继承关系有多复杂,类的__mro__属性,都可以明确的指出方法解析的顺序,mro 是 Method Resolution Order的缩写
print(C.__mro__) # (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
你可以可以用C.mro() 获得解析顺序,它返回的是一个列表。在这个顺序里,A更靠近C, 因此c.foo, 调用的是A类的foo方法。
在lazyload源码里,作者在注释里解释说重写了__mro__方法,可在3.6中,__mro__是属性,而非方法,或许在3.4, 3.5版本里,它这样做是ok的。
QQ交流群: 211426309