当前位置: 首页 > 科技观察

Python的lazyimport如何实现-lazyimport

时间:2023-03-18 11:23:49 科技观察

如果你的Python程序import很多,启动很慢,那么你应该试试lazyimports。本文分享一种实现惰性导入的方法。虽然PEP0690[1]已经提出Python编译器(-L)或者标准库添加这个特性,但是目前的Python版本还没有实现。众所周知,Python应用程序在执行用户的实际操作之前,会先进行一次导入操作。不同的模块可能来自不同的位置,有的模块运行起来可能非常耗时,有的模块可能根本就不会被用户调用,那么多模块的导入纯粹是浪费时间。所以我们需要惰性导入。应用惰性导入时,运行importfoo只会将名称foo作为惰性引用添加到全局全名称空间(globals())。编译器遇到任何访问foo的代码,只有在执行代码时才会进行真正的导入操作。同理,fromfooimportbar会将bar添加到命名空间中,遇到调用bar的代码就会importfoo。怎么写代码来实现呢?其实不需要写代码来实现。已经有一个项目实现了惰性导入功能。那就是TensorFlow。它的代码不依赖于任何第三方库。我把它放在这里。以后需要lazyimport的时候,直接把LazyLoader[2]类复制到自己的项目中即可。源代码如下:#Codecopiedfromhttps://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/util/lazy_loader.py"""ALazyLoaderclass."""from__future__importabsolute_importfrom__future__importdivisionfrom__future__importprint_functionimportimportlibimporttypesclassLazyLoader(types.ModuleType):"""延迟导入模块,主要是为了避免引入大的依赖项。`contrib`和`ffmpeg`是大模块的示例,并不总是需要,这允许它们仅在使用时加载。"""#这里的lint错误是不正确的。def__init__(self,local_name,parent_module_globals,name):#pylint:disable=super-on-old-classself._local_name=local_nameself._parent_module_globals=parent_module_globals超级(LazyLoader,self).__init__(name)def_load(self):#导入目标模块并将其插入父级的命名空间module=importlib.import_module(self.__name__)self._parent_module_globals[self._local_name]=module#更新这个对象的字典,这样如果有人保留对LazyLoader的引用,查找是有效的(__getattr__只在查找失败时调用)。self.__dict__.update(module.__dict__)返回模块def__getattr__(self,item):module=self._load()returngetattr(module,item)def__dir__(self):module=self._load()returndir(module)代码说明:类LazyLoader继承自types.ModuleType,初始化函数保证惰性模块会像真正的模块一样被正确添加到全局变量中。只有在实际使用模块时,即执行__getattr__或__dir__时,才会导入实际模块,更新全局变量指向实际模块,并将其所有状态(__dict__)更新为实际的模块,这样引用懒加载,加载的模块不需要每次访问都被加载过程代码使用:通常我们这样导入模块:import对应的懒导入版本的tensorflow.contribascontrib如下:contrib=LazyLoader('contrib',globals(),'tensorflow.contrib')PEP0690建议的做法PEP0690的建议是在编译器(C代码)级别实现的,因此性能会更好。有两种使用方法。一种方法是在执行Python脚本时添加-L参数。例如有两个文件spam.py,内容如下:importtimetime.sleep(10)print("spamloaded")egg.py内容如下:importspamprint("importsdone")在正常导入条件下,它将在10秒后首先打印“spamloaded”,然后打印“importsdone”。在执行python-Leggs.py的时候,spam模块是绝对不会导入的,spam模块根本就没有用到。如果egg.py的内容如下:importspamprint("importsdone")spam会在执行python-Leggs.py时先打印"importsdone",10秒后打印"spamloaded")。另一种方式是调用标准库importlib:importimportlibimportlib.set_lazy_imports(True)如果有些模块不能延迟加载,需要排除,可以importimportlibimportlib.set_lazy_imports(True,excluding=["one.mod","another"])也可以是这样的:fromimportlibimporteager_importswitheager_imports():importfooimportbar最后的话已经有专业人士在真实的Python命令行程序上测试过了。应用lazyimport后,启动时间可以增加70%%,内存占用减少40%,非常可观。参考文献[1]PEP0690:https://github.com/python/peps/blob/main/pep-0690.rst[2]LazyLoader:https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/util/lazy_loader.py