lazy_import源码解析(原创)

时间:2022-06-13 16:03:24

参考链接:

An approach to lazy importing in Python 3.7(这个是参考源)

Python3.7中一种懒加载的方式(中文翻译)

原博客核心:

以前的两种惰性/延迟加载方法:

  ①本地子功能区加载而非程序启动时的全局加载。直到你的程序运行需要这个库的时候才进行加载;缺点:易重复载入库文件、容易遗忘库载入的范围。

  ②惰性加载。需要模块的时候触发 ModuleNotFoundError  提前发现这个模块,而延迟的只是后续补加载过程;缺点:显式优于隐式、如果一个模块希望立即加载,那么在延迟加载时,它可能会严重崩溃。(Mercurial实际上开发了一个模块黑名单,以避免延迟加载来解决这个问题,但是他们必须确保对其进行更新,因此这也不是一个完美的解决方案。)

 

博主提出来的最新的方法:

 

  在Python 3.7中,模块现在可以在其上定义__getattr__(),允许编写一个函数,在模块上的属性不可用时导入模块。这样做的缺点是使它成为一个惰性导入而不是一个加载,因此很晚才发现是否会引发ModuleNotFoundError。但是它是显式的,并且仍然是为您的模块全局定义的,因此更容易控制。

改进方向:发现导入错误被推迟,如何提前获知这个可能出现的导入错误防止程序抛出异常并终止。

 

代码段1:demo1.py

 1 import importlib
 2 
 3 # 这个是实现lazy_import的功能函数
 4 def lazy_import(importer_name, to_import):
 5     module = importlib.import_module(importer_name) # 直接加载调用的后一级函数
 6 
 7     import_mapping = {} # 字典 键名:有可能为缩写名  值名:为原始可查找库名,例如:import_mapping['np'] = 'numpy'
 8     for name in to_import:
 9         importing, _, binding = name.partition(' as ')
10         if not binding:
11             _, _, binding = importing.rpartition('.')
12         import_mapping[binding] = importing
13 
14     def __getattr__(name):
15         if name not in import_mapping:  # 如果这个库没在import_mapping中,就抛出异常错误,并且中断
16             message = f'module {importer_name!r} has no attribute {name!r}'
17             raise AttributeError(message)
18         importing = import_mapping[name]
19         imported = importlib.import_module(importing,module.__spec__.parent)
20         # print('name=',name,'module=',module,'module.__spec__=',module.__spec__,'module.__spec__.parent=',module.__spec__.parent)
21         setattr(module, name, imported) # sub, np, numpy
22         return imported
23 
24     return module, __getattr__  #返回一个库和一个方法

 

 

代码段2:sub.py

lazy_import 在python3.7中已经可以直接使用了
 1 # In pkg/__init__.py with a pkg/sub.py.
 2 import demo1
 3 
 4 # print('sub.py中的__name__ =', __name__)  #当其他程序调用这个程序的时候 __name__ = 'sub',自己为主程序的时候为 '__main__',这里我第一次使用的时候就出错了,直接在这个程序中测试
 5 mod, __getattr__ = demo1.lazy_import(__name__, {'sys', '.sub as thingy', 'numpy as np'})
 6 
 7 
 8 def test1():
 9     print('sys运行正常')
10     return mod.sys
11 
12 
13 def test2():
14     return mod.thingy.answer
15 
16 
17 def test3():
18     print('numpy运行正常')
19     return mod.np

 

代码段3:mid_test.py

 1 import sub
 2 
 3 ### 异常检测,str_out是不存在的,抛出异常处理
 4 # module1 = sub.str_out()
 5 
 6 module2 = sub.test3()
 7 print(module2)
 8 # <module 'numpy' from 'C:\\ProgramData\\Anaconda3\\envs\\lib\\site-packages\\numpy\\__init__.py'>
 9 
10 print(module2.array([1, 2, 3, 4]))
11 # [1 2 3 4]
12 
13 print(module2.__spec__)
14 # ModuleSpec(
15 # name='numpy',
16 # loader=<_frozen_importlib_external.SourceFileLoader object at 0x000001E4879A6F98>,
17 # origin='C:\\ProgramData\\Anaconda3\\envs\\lib\\site-packages\\numpy\\__init__.py',
18 # submodule_search_locations=['C:\\ProgramData\\Anaconda3\\envs\\lib\\site-packages\\numpy'])
19 
20 print(module2.__spec__.parent)
21 # numpy