Python模块和导入介绍
站内链接:
模块,包,文件
关系
- 模块
自我包含并且有组织的代码片段
- 包
package 是一个有层次目录结构, 包含子包和模块的 Python 应用程序执行环境, 一种特殊的模块. 任何包含__path__
(包所在的路径)属性的对象都可以称为 package: django.__path__
他们之间的关系如下:
- file—物理组织
- module—逻辑组织
- package—执行环境与目录结构(
__init__.py
)
绝对和相对导入
全限定名: 模块的绝对路径含义, 确保 python 虚拟机在寻找某一个 module 时, 根据
__package__ + . + __name__
来定位.load: python interpreter 对于 import 和 running 使用两种不同的加载方式进行区分, 其会阻止后者进行相对导入工作. 换言之, 如果允许后者能够进行相对导入工作, 那么
PYTHONPATH,sys.path
其中没有那么大的必要, 这样会影响代码的整体组织结构.path: 对于
foo.bar.baz
路径, 导入逻辑或者步骤:foo --> foo.bar --> foo.bar.baz
python 的导入不同于 java 的 import, 后者仅仅是告知 Java 编译器需要特定的包, 这是大部分人的理解, 但是 python 的的首次 import 会执行导入模块的所有顶层代码
, 甚至可以做所有running运行时
才能够做到的事情, 关于import
和running
的说明和区别见下文说明.
包相关术语
模块(module)是 Python 中代码可重用性的基本单元, 模块分为三个类型: 纯 python 模块, 扩展模块, 包
- pure python module: 用 Python 编写并包含在单个
.py或.pyc
文件中的模块 - extension module: 用 python 实现的低级语言编写的模块, 比如
c/c++ for python, java for python
, 其通常包含在单个可动态加载的预编译文件中, 例如 so 文件 - package: 包含其他模块的模块
另外, 还有一个root package
的概念, 表示包层次结构的根
, 这并非真正的包, 因为其不包含__init__.py
.
模块导入方法
导入流程
路径导入流程:
- 每一次 import 的路径搜索流程:
sys.modules-->current directory module-->sys.path
, 其中sys.modules
包含了 python 开始运行其所有已经导入的模块
- 每一次 import one package 的过程实际就是执行
__init__
.py 的过程 - 每一次 execute init 的过程: 如果没有在
__init__
.py 中导入的话, 不会默认加载 module_name 目录下的其他模块.
模块导入是一个延迟导入, 按需导入的过程, 所以从性能上讲, 没必要将所有的子模块都在__init__
.py 中导入, 除非尽可能的低耦合. 对于字符串描述的模块路径, 使用 importlib 导入: importlib.import_module('math')
, 相反, 对于相对导入, 该语句必定是在 package 中: importlib.import_module('.b', package__)
导入术语
- 已导入模块缓存
sys.modules
存放当前导入的所有模块的缓存信息, 每一次 finder 开始搜索模块时都会先搜索该缓存, 如果发现则直接返回.sys.modules
是一个字典, 其是可写的, 其中reload(), importlib.reload()
会重新执行该缓存中已有的某个模块.
- Finder and Loader
finder 的动作在搜索sys.module
之后, finder 根据所知的所有策略, 找到指定的模块(定位内置模块), 判断是否冻结模块, 并获取模块的导入路径, 模块相关元信息, 返回 loader.
- 其中导入路径可以为: 文件系统路径, 文件系统压缩文件, URLs 识别的资源.
- 查找器有多个, 扩展自定义的 finder 可以扩展模块的搜索范围和广度, 见 3.4 节说明.
loader: 根据 finder 返回的信息进行加载操作并执行–模块执行
1 | # 1 load module(存放-设置) |
- 导入钩子程序
扩展 finder 的基础就是: 导入钩子程序, 其分类如下
- 元钩子程序
- 导入路径钩子程序
其中元钩子程序是一个内建模块, 其覆盖sys.path
, 我们可以使用sys.meta_path
完成元钩子注册. 如果希望导入路径钩子程序
, 则需要使用sys.path
完成扩展注册.
导入和运行时
导入流程
导入一个包import pa
时会执行的代码流程如下:
1 执行 pa 模块中的所有顶层代码
2 执行 pa 模块中的导入的其他模块的顶层代码, 其中 a 和 b 步骤是按照代码顺序往下执行的
3 在执行顶层代码时: 若碰到类定义则执行类的
定义体
, 若碰到函数则编译函数
, 若碰到装饰器则执行装饰器函数定义体
4 执行类定义体的时候
- a. 若执行
嵌套类
, 则会执行嵌套类的定义体, 之后规则就会重复 4, 一直递归到不存在嵌套类 - b. 若类被装饰, 则会在执行完
类定义体
之后再执行装饰器函数定义体
- a. 若执行
5 执行装饰器函数定义体, 若是嵌套函数则仅仅编译函数
上面所有情况的实例代码bambooimport.py
如下:
1 | print('执行顺序1: 顶层代码print') |
则执行import bambooimport
时的输出如下:
1 | In [1]: import bambooimport |
那么, 运行时
流程同导入流程
有什么区别呢? 运行时会真正执行函数定义中的代码, 对于入口 main 还会执行__main__
中的代码, 这些代码在导入时是不会执行的.
模块名
对于上面的代码, 在顶层代码中增加一行print(f"此时 __name__ 变量的值是:{repr(__name__)}")
, 则导入和运行时他们的输出是不同的
1 | # 1. 运行时 |
另外, 因为模块缓存的存储, 重复import bambooimport
并没有输出, 而python bambooimport.py
会一直重复输出.