importlib — The implementation of import — Python 3.11.3 documentation
目录
一、说明
二、 模块导入简介
2.1 最简单的 importlib用途
2.2 importlib 包的目的有三个
2.3 import_module() 和__import__()
三、高级模块使用
3.1 动态引入
3.2 模块引入检查
3.3 从源文件中引入
3.4 import_from_github_com
四、总结
一、说明
关于python的模块导入,我早就想介绍一下,近期从一个新项目中发现了这种用法频频出现,感觉有必要说道说道。我们知道,在3.3以上的python版本已经不用__init__.py作为包的标志,因而importlib导入包就必须知道,万一哪天__init__.py废了尚且不知,那就搞笑了。
参考:
【python知识】__init__.py的来龙去脉
二、 模块导入简介
2.1 最简单的 importlib用途
请看代码,如下示例:
import numpy as np
ss = np.array([2,3,4,5,6])
print(ss)
>>>
[2,3,4,5,6]
上面语句,如果用importlib将如下实现:
import importlib
np = importlib.import_module('numpy')
ss = np.array([2,3,4,5,6])
print(ss)
好了,对于不关注更多功能的人们,到此已经学完了。
2.2 importlib 包的目的有三个
- 一种是在 Python 源代码中提供 import 语句的实现(因此,通过扩展,提供 __import__() 函数)。这提供了一个可移植到任何 Python 解释器的 import 实现。这也提供了一种比用 Python 以外的编程语言实现的实现更容易理解的实现。
- 第二,实现导入的组件暴露在这个包中,使用户更容易创建自己的自定义对象(通常称为导入器)以参与导入过程。
- 第三,该包包含公开用于管理 Python 包方面的附加功能的模块:
Python提供了importlib包作为标准库的一部分。目的就是提供Python中import语句的实现(以及__import__函数)。另外,importlib允许程序员创建他们自定义的对象,可用于引入过程(也称为importer)。其特点有:
- 动态引入
- 检查模块是否可以被引入
- 引入源文件自身
- 第三方模块 import_from_github_com
2.3 import_module() 和__import__()
导入一个模块。 name 参数以绝对或相对术语指定要导入的模块(例如 pkg.mod 或 ..mod)。如果以相对术语指定名称,则包参数必须设置为包的名称,该包将充当解析包名称的锚点(例如 import_module('..mod', 'pkg.subpkg')将导入 pkg.mod)。
import_module() 函数充当 importlib.__import__() 的简化包装器。这意味着该函数的所有语义都派生自 importlib.__import__()。这两个函数之间最重要的区别是 import_module() 返回指定的包或模块(例如 pkg.mod),而 __import__() 返回顶级包或模块(例如 pkg)。
__import__的例子:
__import__是python的一个内置方法,直接调用__import__()即可获取一个模块.
testImport.py:
mName = "demo"
module = __import__(mName)
module.getName()
如果您正在动态导入自解释器开始执行以来创建的模块(例如,创建 Python 源文件),您可能需要调用 invalidate_caches() 以便导入系统注意到新模块。
三、高级模块使用
3.1 动态引入
importlib模块支持传入字符串来引入一个模块。我们创建两个简单的模块来验证这个功能。我们将会给予两个模块相同的接口,让它们打印名字以便我们能够区分它们。创建两个模块,分别为foo.py和bar.py,代码如下所示,
def main():
print(__name__)
现在我们使用importlib来引入它们。让我们看看这段代码如何去做的。确保你已经把这段代码放在与上面创建的两个模块相同的目录下。
import importlib
def dynamic_import(module):
return importlib.import_module(module)
if __name__ == "__main__":
module = dynamic_import('foo')
module.main()
module_two = dynamic_import('bar')
module_two.main()
以上代码告诉我们:既然模块可以通过它的名称字符串引入,就可以在方便时调用,无需全部写在文件前头。
在这段代码中,我们手动引入importlib模块,并创建一个简单的函数dynamic_import。这个函数所做的就是调用importlib模块中的import_module函数,入参就是我们传入的字符串,然后返回调用结果。
3.2 模块引入检查
Python有一个编码规范就是EAPP:Easier to ask for forgiveness than permision。意思就是经常假设一些事情是存在的(例如,key在词典中),如果出错了,那么就捕获异常。你可以看 Python标准模块–import 文章中我们尝试引入模块,当它不存在时,我们就会捕获到ImportError。如果我们想检查并观察一个模块是否可以引入而不是仅仅是猜测,该如何去做?你可以使用importlib。
importlib.util参考代码:
import importlib.util
import importlib
def check_module(module_name):
module_spec = importlib.util.find_spec(module_name)
if module_spec is None:
print("Module :{} not found".format(module_name))
return None
else:
print("Module:{} can be imported!".format(module_name))
return module_spec
def import_module_from_spec(module_spec):
module = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(module)
return module
if __name__ == "__main__":
module_spec = check_module("fake_module")
module_spec = check_module("collections")
if (module_spec):
module = import_module_from_spec(module_spec)
print(dir(module))
示例2:
import importlib.util
import sys
# For illustrative purposes.
name = 'itertools'
if name in sys.modules:
print(f"{name!r} already in sys.modules")
elif (spec := importlib.util.find_spec(name)) is not None:
# If you chose to perform the actual import ...
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
print(f"{name!r} has been imported")
else:
print(f"can't find the {name!r} module")
这里我们引入importlib模块的子模块util。在check_module函数中,我们调用find_spec函数来检查传入的字符串作为模块是否存在。
- 如果模块未安装,find_spec函数将会返回None。
- 也可以获取到模块的说明,或者你可以将字符串传入到import_module函数中.参看一下上述代码中的import_module_from_spec函数
3.3 从源文件中引入
在这一节中,我想说明importlib的子模块util还有另外一个技巧。你可以使用util通过模块名和文件路径来引入一个模块。
示例1,
import importlib.util
def import_source(module_name):
module_file_path = module_name.__file__
module_name = module_name.__name__
module_spec = importlib.util.spec_from_file_location(module_name ,module_file_path)
module = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(module)
print(dir(module))
msg = "The {module_name} module has the following methods:{methods}"
print(msg.format(module_name = module_name ,methods = dir(module)))
if __name__ == "__main__":
import logging
import_source(logging)
示例2
import importlib.util
import sys
# For illustrative purposes.
import tokenize
file_path = tokenize.__file__
module_name = tokenize.__name__
spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
上述代码中,我们实际引入了logging模块,并将它传入到import_source函数。在这个函数中,我们首先获取到模块的实际路径和名称。然后我们将这些信息传入到util的spec_from_file_location函数中,这个将会返回模块的说明。一旦我们获取到模块的说明,我们就可以使用与2.2节相同的importlib机制来实际引入模块。
现在让我们来看一个精巧的第三方库,Python的__import__()函数直接引入github中的包。
3.4 import_from_github_com
这个精巧的包叫做import_from_github_com,它可以用于发现和下载github上的包。为了安装他,你需要做的就是按照如下命令使用pip,
pip install import_from_github_com
这个包使用了PEP 302中新的引入钩子,允许你可以从github上引入包。这个包实际做的就是安装这个包并将它添加到本地。你需要Python 3.2或者更高的版本,git和pip才能使用这个包。
一旦这些已经安装,你可以在Python shell中输入如下命令,
>>> from github_com.zzzeek import sqlalchemy
Collecting git+https://github.com/zzzeek/sqlalchemy
Cloning https://github.com/zzzeek/sqlalchemy to /tmp/pip-acfv7t06-build
Installing collected packages: SQLAlchemy
Running setup.py install for SQLAlchemy ... done
Successfully installed SQLAlchemy-1.1.0b1.dev0
>>> locals()
{'__builtins__': <module 'builtins' (built-in)>, '__spec__': None,
'__package__': None, '__doc__': None, '__name__': '__main__',
'sqlalchemy': <module 'sqlalchemy' from '/usr/local/lib/python3.5/site-packages/\
sqlalchemy/__init__.py'>,
'__loader__': <class '_frozen_importlib.BuiltinImporter'>}
你如果看了import_from_github_com的源码,你将会注意到它并没有使用importlib。实际上,它使用了pip来安装那些没有安装的包,然后使用Python的__import__()函数来引入新安装的模块。
四、总结
这里说了部分的importlib功能,更深刻的也有,请大家参照:
importlib — The implementation of import — Python 3.11.3 documentation我个人认为先入点门,以后遇到高深问题再详细追究不迟。