python之import机制详解

本文详述了python的import机制,对于理解python的运行机制很有帮助!

1.标准import:

python中所有加载到内存的模块都放在 sys.modules 。当 import 一个模块时首先会在这个列表中查找是否已经加载了此模块,如果加载了则只是将模块的名字加入到正在调用 import 的模块的 local 名字空间中。如果没有加载则从 sys.path 目录中按照模块名称查找模块文件,模块可以是py、pyc、pyd,找到后将模块载入内存,并加到 sys.modules 中,并将名称导入到当前的 local 名字空间。

一个模块不会重复载入。多个不同的模块都可以用 import 引入同一个模块到自己的 local 名字空间,其实背后的 pymoduleobject 对象只有一个。这里说一个容易忽略的问题:import 只能导入模块,不能导入模块中的对象(类、函数、变量等)。例如:模块 a(a.py)中有个函数 getname,另一个模块不能通过 import a.getname 将 getname导入到本模块,只能用 from a import getname。

2.嵌套import:

1)顺序嵌套

例如:本模块导入 a 模块(import a),a 中又 import b,b 模块又可以 import 其他模块……
这中嵌套比较容易理解,需要注意的一点就是各个模块的 local 名字空间是独立的。对于上面的例子,本模块 import a 之后本模块只能访问模块 a,不能访问模块 b 及其他模块。虽然模块 b 已经加载到内存了,如果访问还要再明确的在本模块中 import b。

2)循环嵌套

例如:

文件[a.py]

from b import d
class c:pass

文件[ b.py ]

from a import c
class d:pass

为什么执行 a 的时候不能加载 d 呢?
如果将 a.py 改为:import b 就可以了。
这是怎么回事呢?

robertchen:这跟python内部 import 的机制是有关的,具体到 from b import d,python 内部会分成几个步骤:
(1)在 sys.modules 中查找符号 “b”
(2)如果符号 b 存在,则获得符号 b 对应的 module 对象。
从 的 __dict__ 中获得符号 “d” 对应的对象,如果 “d” 不存在,则抛出异常。
(3)如果符号 b 不存在,则创建一个新的 module 对象 ,注意,此时,module 对象的 __dict__ 为空。
执行 b.py 中的表达式,填充 的 __dict__。
从 的 __dict__ 中获得 “d” 对应的对象,如果 “d” 不存在,则抛出异常。

所以这个例子的执行顺序如下:

1、执行 a.py 中的 from b import d 由于是执行的 python a.py,所以在 sys.modules 中并没有 存在, 首先为 b.py 创建一个 module 对象 () , 注意,这时创建的这个 module 对象是空的,里边啥也没有, 在 python 内部创建了这个 module 对象之后,就会解析执行 b.py,其目的是填充 这个 __dict__。
2、执行 b.py中的from a import c 在执行b.py的过程中,会碰到这一句, 首先检查sys.modules这个module缓存中是否已经存在了, 由于这时缓存还没有缓存, 所以类似的,python内部会为a.py创建一个module对象(), 然后,同样地,执行a.py中的语句
3、再次执行a.py中的from b import d 这时,由于在第1步时,创建的对象已经缓存在了sys.modules中, 所以直接就得到了, 但是,注意,从整个过程来看,我们知道,这时还是一个空的对象,里面啥也没有, 所以从这个module中获得符号”d”的操作就会抛出异常。 如果这里只是import b,由于”b”这个符号在sys.modules中已经存在,所以是不会抛出异常的。

zq:图解如下:

3. 包 import

只要一个文件夹下面有个 __init__.py 文件,那么这个文件夹就可以看做是一个包。包导入的过程和模块的基本一致,只是导入包的时候会执行此包目录下的 __init__.py 而不是模块里面的语句了。另外,如果只是单纯的导入包,而包的 __init__.py 中又没有明确的其他初始化操作,那么此包下面的模块是不会自动导入的。
例如:
有下面的包结构:
pa
|—- __init__.py
|—- wave.py
|—- pb1
|—- __init__.py
|—- pb1_m.py
|—- pb2
|—- __init__.py
|—- pb2_m.py
有如下程序:

import sys
import pa.wave #1
import pa.pb1 #2
import pa.pb1.pb1_m as m1 #3
import pa.pb2.pb2_m #4
pa.wave.getname() #5
m1.getname() #6
pa.pb.pb2_m.getname() #7

1) 当执行 #1 后,sys.modules 会同时存在 pa、pa.wave 两个模块,此时可以调用 pa.wave 的任何类或函数了。但不能调用 pa.pb1(2) 下的任何模块。当前 local 中有了 pa 名字。

2) 当执行 #2 后,只是将 pa.pb1 载入内存,sys.modules 中会有 pa、 pa.wave、pa.pb1 三个模块,但是 pa.pb1 下的任何模块都没有自动载入内存,此时如果直接执行 pa.pb1.pb1_m.getname() 则会出错,因为 pa.pb1 中并没有 pb1_m 。当前 local 中还是只有 pa 名字,并没有 pa.pb1 名 字。

3) 当执行 #3 后,会将 pa.pb1 下的 pb1_m 载入内存,sys.modules 中会有 pa、pa.wave、pa.pb1、pa.pb1.pb1_m 四个模块,此时可以执行 pa.pb1.pb1_m.getname() 了。由于使用了 as,当前 local中除了 pa 名字,另外添加了 m1 作为 pa.pb1.pb1_m 的别名。

4) 当执行 #4 后,会将 pa.pb2、pa.pb2.pb2_m 载入内存,sys.modules 中会有 pa、pa.wave、pa.pb1、pa.pb1.pb1_m、pa.pb2、pa.pb2.pb2_m 六个模块。当前 local 中还是只有 pa、m1。
下面的 #5,#6,#7 都是可以正确运行的。

注意的是:如果 pa.pb2.pb2_m 想导入 pa.pb1.pb1_m、pa.wave 是可以直接成功的。最好是采用明确的导入路径,对于 ./.. 相对导入路径还是不推荐用。

Posted in 未分类