20190115更新:一篇不错的文章: 《Python 3.x可能是史上最详解的【导入(import)】(https://blog.csdn.net/weixin_38256474/article/details/81228492)》
###引子:python中 from . import ×××的那个点是表示当前包吗?
我的理解是 from . import XXX默认的就是在当前程序所在文件夹里__init__.py程序中导入XXX,如果当前程序所在文件夹里没有__init__.py文件的话,就不能这样写,而应该写成from .A import XXX,A是指当前文件夹下你想导入的函数(或者其他的)的python程序名,如果你想导入的函数不在当前文件夹,那么就有可能用到 from … import XXX(即上一个文件夹中的__init__.py),或者from …A import XXX(即上一个文件夹中的文件A)
##Python的两种引入机制
Python 提供了二种引入机制:
from .string import a
from ..string import a
from ...string import a
from pkg import foo
from pkg.moduleA import foo
要注意的是,需要从包目录最顶层目录依次写下,而不能从中间开始。
由于相对import已经不再使用,后面内容只针对绝对miport。
##模块与包的定义
模块:本质就是.py结尾的文件(逻辑上组织python代码)模块的本质就是实现一个功能 文件名就是模块名称。
包: 一个有__init__.py的文件夹;用来存放模块文件
##导入模块
基本格式:
import 模块名
form 模块名 import *
from 模块名 import 模块名 as 新名称
导入模块的本质:
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。
为什么执行 A 的时候不能加载 D 呢?
如果将 A.py 改为:import B 就可以了。
这是怎么回事呢?
这跟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。(在本例中,B.py中又会要求导入A,所以无法完成对_dict_的填充)
从 的 dict 中获得 “D” 对应的对象,如果 “D” 不存在,则抛出异常。(B.py中的from A import C没执行完,‘class D:pass’无法执行,D也就无法在__dict__中找到)
所以这个例子的执行顺序如下:
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中已经存在,所以是不会抛出异常的。
##导入包
导入包的本质:导入一个包 就是执行包下的__init__.py文件
只要一个文件夹下面有个 init.py 文件,那么这个文件夹就可以看做是一个包。包导入的过程和模块的基本一致,只是导入包的时候会执行此包目录下的 init.py 而不是模块里面的语句了。另外,如果只是单纯的导入包,而包的 init.py 中又没有明确的其他初始化操作,那么此包下面的模块是不会自动导入的。
例如:
有下面的包结构:
PA
有如下程序:
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 后,sys.modules 会同时存在 PA、PA.wave 两个模块,此时可以调用 PA.wave 的任何类或函数了。但不能调用 PA.PB1(2) 下的任何模块。当前 Local 中有了 PA 名字。
当执行 #2 后,只是将 PA.PB1 载入内存,sys.modules 中会有 PA、 PA.wave、PA.PB1 三个模块,但是 PA.PB1 下的任何模块都没有自动载入内存,此时如果直接执行 PA.PB1.pb1_m.getName() 则会出错,因为 PA.PB1 中并没有 pb1_m 。(如果只是单纯的导入包,而包的 init.py 中又没有明确的其他初始化操作,那么此包下面的模块是不会自动导入的。)当前 Local 中还是只有 PA 名字,并没有 PA.PB1 名 字。
当执行 #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 后,会将 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 都是可以正确运行的。
##python搜索路径
对搜索路径的操作
import sys,os
os.path.abspath(__file__) #获取当前文件的全名
os.path.dirname() #获取当前对象的父级目录
sys.path.insert()#将当前对象的路径添加到首位
sys.path.append() # 将当前环境变量添加到环境变量的末尾
参考文章
python之import机制详解
http://www.jb51.net/article/51815.htm
http://www.jb51.net/article/118358.htm
浅谈python import引入不同路径下的模块
http://www.jb51.net/article/118367.htm
上文所描述的包导入方法,在pycharm中可能会失效,这是因为pycharm IDE的问题,IDE默认根目录是项目所在的路径。而不是第一个具有"init"文件的路径。
如下图所示的情况,如果使用“from pack1 import pack1_file2”会报错(ModuleNotFoundError: No module named ‘pack1’)。正确的绝对路径方式是:from TGS_salt.importtest.pack1 import pack1_file2。
此外,可以通过修改pycharm项目的根目录来调整。方法参考 https://blog.csdn.net/u014496330/article/details/55211398
解决方法
(1)改成相对路径
from …package import *
第一个.表示当前目录,后面的每一个’.’表示上一层目录。用相对目录可以保证import成功,但是不建议这种写法,因为如果当前这个文件要移动到其他包的话,就要改很多地方了,当然,使用相对路径表示可以随意更改包名,只要保证
(2)在pycharm中设置source路径
file–>setting–>project:server–>project structure
将放package的文件夹设置为source,这样import的模块类等,就是通过这些source文件夹作为根路径来查找,也就是在这些source文件夹中查找import的东西。