python中的Import总结
动机
最近的一些基于python的框架型的工作,有很多涉及到动态用户代码加载的功能。因此,对python的导入(import)做一个梳理总结。
基础概念
- 模块(module):一般是指python源代码文件,即
.py
文件。还有可能是:.pyo
、.pyc
、.pyd
、.so
文件。 - 包(package):包是含有module的文件夹,为了避免模块名冲突而引入的。当一个文件夹下有
__init__.py
时,代表这个文件夹是一个package。package也可以看做是特殊的module,它的替身就是对应的__init.py__
文件。 - 命名空间(namespace):
local namespace
:每个函数function特有,用于保存函数的变量。可用内置函数locals()
查看,该函数不可写。enclosing function namespace
:闭包命名空间:闭包函数 的名称空间(Python 3 引入)。global namespace
:每个模块module特有,用于保存模块的变量。可用内置函数globals()
查看,该函数可写。builtin namespace
:内建命名空间:Python 解释器启动时自动载入__built__
模块后所形成的名称空间;如str/list/dict
等内置对象的名称就出于这里。
相关的内置属性:
__all__
:一般在__init__.py
中用于指定此package在被外部代码import时,哪些module会被引进当前作用域中。__name__
:直接运行本module,值为__main__
;import module
,值为module名字。__path__
:保存当前package的路径。修改__path__
可以改变package内的搜索路径。任何具有__path__
属性的模块都会被当作是包。__file__
:如果是package,则保存__init__.py
的绝对路径;如果是module,返回module本身的绝对路径。__package__
:保存module对应的package名称。
import
import
语句时发起导入机制的最常用方式,会调用内置的__import__()
。包括以下两个操作:
- 搜索指定名称的模块。
- 将搜索结果绑定到当前作用域中。
搜索
- 先检查
sys.modules
中是否有模块缓存;
- 先检查
- 如果没有缓存,会接着搜索
sys.meta_path
路径下的查找器再进行查找。
- 如果没有缓存,会接着搜索
- 查找器会使用
sys.path
来搜索模块。sys.path
会将环境变量PYTHONPATH
指定的路径添加进去,并且path[0]
会给脚本存放的路径保留。
- 查找器会使用
加载
- 如果之前没有缓存,会将模块加入到
sys.modules
中。
- 如果之前没有缓存,会将模块加入到
- 注册到当前module的全局命名空间中。
importlib
1 | importlib.import_module(name, package=None) |
在实践中,我会经常用importlib去加载用户指定的代码,比如user_project/module.py
,可以用importlib.import_module("user_project.module")
来导入。另外需要确保user_project
是包含在sys.path
的。
exec
exec
和eval
都可以用来动态地执行python代码,区别是前者可以是大段的代码;后者是一个python表达式。
因此,用户的代码片段,使用exec
来动态加载也是一个不错的选项。
需要注意的是,exec
除了接收代码对象外,还接受执行的上下文:exec(object[, globals[, locals]])
,也就是两个重要的参数:globals
和locals
。
globals
用来指定代码执行时可以使用的全局变量以及收集代码执行后的全局变量。而且默认会加上__builtins__
。locals
用来指定代码执行时的局部变量以及收集代码执行后的局部变量。
上面的描述不能完全表达这两个参数的逻辑,注意理解下面代码的逻辑:
1 | In [1]: code = """ |
总结:代码片段中新增加的变量,都保存在locals
中;若只传一个globals
参数,则locals
也会使用globals
传入的参数,所以这时globals
中会有代码片段执行后新增加的变量。至于为什么新增的变量会只在locals
中,可以这么理解:exec
中执行的代码是在函数exec_xxx()
中执行,而不是直接在当前上下文执行;如果需要达到当前上下文执行的效果,就把globals()
传给locals
参数即可。