Python-模块导入逻辑详解
正式开坑分享python啦~
从最开头讲起吧,说一下python里导入一个模块的逻辑
文章目录
- 概念明确
- 三种模块导入方式
- 模块导入详细流程
概念明确
- module: 模块,一般指一个.py文件,实际还可能是".pyo"、".pyc"、".pyd"、".so"、".dll"
- package: 包,指一个包含
__inin__.py
的目录。(并且这个目录不是__main__
所在的目录,因为python解释器不会把当前目录当做package) -
<type 'module'>
: 每个import过来的module,都是一个module类型的实例,如<module 'sys' (built-in)>
- sys.module: 这是一个超全局的dict,里面存的是从开启python解释器以来,所有被import的module,可以理解成一个只存module的全局globals
- globles, locals: 类似与当前文件的全局变量合集和局部变量合集,就不详细展开了
- 本文使用环境: python2.7,暂不清楚py3会有什么不同
模块引用方式
一共有三个方式引用一个模块:
from path import module
reload(module)
__import__(module, [globals={}, locals={}, fromlist=[], level=-1])
第一种: 直接import
最常见的模块引用方式,不多说了,一般写作import module
或 from path import module
后一种的path,可以是相对路径,这里会引出两个问题来:
- 一个.py文件,如果使用了相对路径import,那么这个文件是无法被直接执行的,也就是无法作为
__main__
来执行,强行执行的话会报错ValueError: Attempted relative import in non-package
。原因是,相对路径是使用module.__name__
来实现的,直接运行的话,__name__ = '__main__'
,相对路径找不到原本的package,所以会报错 - 如果一个文件使用相对路径引用到了
__main__
所在的目录,或者更上层目录,那么也会报错,错误信息是ValueError: Attempted relative import beyond toplevel package
。原因见package的定义,而且这个报错也很符合直觉
第二种: reload()
重新加载一个模块,一般只会在比较特殊的地方使用
为什么要重新加载呢,因为如果在A里import了B,C里在importA和B的时候,B并没有被重新加载,而是直接把A里的B的内存地址传给了C的本地环境(即直接取了sys.module里的,详见流程介绍),而如果B模块有变化的话,就需要重新加载B
另外一种情况是sys.setdefaultencoding('utf8')这种,这个函数在每次启动python解释器时都会运行,然后就会被删除掉,所以当我们import sys时其实已经没有这个函数了,所以必须reload(sys)一次,才能使得这个函数重新可用。(顺便一提,默认sys.getdefaultencoding()是ascii)
另外,只有之前import过的module,才能reload,原因见下面reload流程解释
第三种: __import__()
第一种方法实际上就是调用的这个函数,它接收字符串作为参数,我们一般不直接用,但在讲反射的文章里100%会用这个函数举例子
语法就不细讲了,真用到的时候再去看手册也来得及,一般用途是动态载入模块(如web api框架),或延迟模块载入(如放到__getattr__
里),以后可以写一写
模块引用详细流程
import流程
- 在sys.module里找模块名,找到了就将其引用加入本地locals里
- 如果没有找到,则从sys.path里按顺序查找模块文件,找到后先将其名字添加到sys.module,此时这个module的
__dict__
是空的 - 然后执行一遍该文件,并将执行过后的locals填充到sys.module的相应key下。这里可能会有个循环import的坑,下面再说
- 填充完sys.module后,将其引用加入本地locals,如第一步
这可以解释上文留下的一个问题“为什么需要reload”,因为import的时候,只是从sys.module里找出引用写到当前locals里,并不会重新执行一遍原模块
循环导入的坑是咋回事:
直接贴一个别人的链接吧,他那边图文并茂比我讲的好多了: https://www.jb51.net/article/51815.htms
reload流程
- 在sys.module里找到这个模块,找不到会报错
ImportError: reload(): module skrskr not in sys.modules
- 执行一遍对应文件,用执行过程中得到的locals里的属性替换掉原有模块对象的相应属性,注意是替换模块内的属性,这个模块对象本身的内存地址并没有发送变化,即reload前后,他的id并没有发生变化
这也可以顺便解释上文留下的第二个问题“为什么必须先import才能reload”,因为是在sys.module里直接取的对象,再进行的下一步操作。这里说的先import,不一定是在当前文件import的,比如下面的代码是不会报错的,因为os在启动python时已经默认加载进去了
import sys
reload(sys.modules['os'])
模块加载流程
是指生成这个模块对象并塞到sys.module里的过程,因为没什么坑,不太容易影响到正常工作,所以我还没有看...
但是ModuleFinder类的源码是在这个位置,有兴趣的可以研究下它到底是怎么回事: lib/python2.7/modulefinder.py:75#ModuleFinder
文章首发于微信公众号:Woko笔记
突然想起来我还有简书账号,就来这里同步更新一下,欢迎大家来围观~