import声明即用于导入模块,比如import numpy as np,但是涉及复杂工程目录时容易搞得稀里糊涂,于是我专门使用了python3.7来测试并解决import相关问题。
基本定义
-
module:即模块,也就是中各种
.py文件,模块名就是文件名 -
built-in-module:即内置模块,就是在安装python的时候系统编译在python解释器中的,比如
numpy -
package:任何包含
__init__.py文件的文件夹就是一个package。注意根据python document,在python3.3以上中,即使没有__init__.py,文件夹也被自动视作一个package -
object:即对象,在python中,对象可以是函数,类以及变量等。
import与sys.path
如果import导入了一个module,就能运行该module中的所有代码。导入package时,会运行package目录下的__init__.py,然后通过__init__.py运行package目录下的所有module,__init__.py可以是空的,前面讲到在python3.3以上,__init__.py可以没有。
import会自动在sys.path包含的目录中寻找相应的模块或者包,在一个.py脚本被运行时,sys.path会初始化包含以下目录:
PYTHONPATH,即常说的系统环境变量PATH- 默认安装的模块目录,比如
numpy就在此目录中 .py脚本所在的目录,这一点根据脚本所在的目录不同是可以变化的。
常用的import方式
常用的import方式有四种:
import <package>
import <module>
from <package> import <module or subpackage or object>
from <module> import <object>
当然还有
import <module> as # 比如 import numpy as np
import <object> as
场景1:导入系统以及同级目录下的模块
对于以下的工程目录:
test/ # 跟目录
packA/ # package packA
subA/ # subpackage subA
__init__.py
sa1.py
sa2.py
__init__.py
a1.py
a2.py
packB/ # package packB (implicit namespace package)
b1.py
b2.py
other.py
start.py
要在start.py中导入系统numpy模块,以及同级目录下的other模块,只需要:
import numpy
import numpy as np # 通常将numpy重命名为np
import other
然后运行start.py脚本
场景2:导入子目录下的模块
如果需要在start.py中导入a1.py,b1.py以及sa1.py模块,只需要在start.py中:
import packA.a1
import packB.b1
import packA.subA.sa1
如果只需要导入比如a1.py某个函数a1_func(),只需要:
from packA.a1 import a1_func()
from packA.subA.sa1 import sa1_func() # 跟上面同理
注意在start.py和在a1.py导入sa1.py是不同的,在a1.py需要:
import subA.sa1
from subA.sa1 import sa1_func()
from subA import sa2
因为运行a1.py时sys.path的相应目录已经改变,只包含a1.py脚本所在的目录。而对于python3,在start.py是不能跨越子目录直接导入sa1.py的,比如from subA import sa1,但是在python2中可以,下面做个简单的总结:
| 运行 | from packA.subA import sa1 | from subA import sa1 |
|---|---|---|
| start.py | OK | Py2 OK, Py3 fail (subA not in test/) |
| a1.py | fail (packA not in test/packA/) | OK |
场景3:导入父目录下的模块
如果需要在a1.py中导入父目录下other.py或者是packB目录下的b1.py,此时就需要对sys.path作出修改了,因为运行a1.py时sys.path包含a1.py所在的目录packA,并不包含目录packB以及父目录test。
修改sys.path需要用到sys以及os模块,首先需要知道怎么获取当前目录以及父目录,在a1.py中如下:
current_path=os.path.dirname(__file__) #当前a1.py所在的目录packA
parent_path=os.path.dirname(os.path.dirname(__file__)) #当前a1.py所在的父目录或者说上级目录test
p_parent_path=os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ##获取上上级目录
下面修改sys.path,对于在a1.py中导入父目录下other.py或者是packB目录下的b1.py,只需要将parent_path加入到```sys.path``即可,
sys.path.append(parent_path)
然后就可以导入other.py以及b1.py了,完整代码如下:
import sys
import os
# print(os.getcwd())
# sys.path.append(os.getcwd())
parent_path=os.path.dirname(os.path.dirname(__file__)) # 获取上级目录
p_parent_path=os.path.dirname(os.path.dirname(os.path.dirname(__file__))) # 获取上上级目录
sys.path.append(parent_path) # 修改sys.path
import other # 导入test下的other
import packB.b1 # 导入b1
import packA.a2 #导入a2
import a2 # 此时sys.path既包含上级目录test也包含当前目录packA,所以跟上面一样
此外顺便提到os.getcwd(),注意该函数是获取当前终端的路径而不是脚本的路径,所以为了避免混淆,建议采用os.path.dirname(__file__)这样的形式。
要点提炼
import最重要的是看sys.path所包含的目录,当我们运行脚本时sys.path会自动包含运行脚本所在的目录- python3中不能越级导入,如果
sys.path只包含test所在目录,导入sa1.py时,只能import packA.subA.sa1,而不能import subA.sa1,也不能import sa1 - 接第二点,如果
sys.path经过修改后,既包含test所在目录,也包含packA所在目录,那么import packA.subA.sa1和import subA.sa1都可以,如果再包含sa1所在目录,那么import sa1也可以 - 第三点可能会造成困惑,一种建议的习惯就是在每个运行的脚本中加入工程的根目录即
test所在目录到sys.path中,然后所有导入按照import packA.subA.sa1的形式从第一级目录追溯到要导入的模块即可,而不是一会import packA.subA.sa1,一会import subA.sa1陷入混淆,只导入当前目录所在的模块依然可以import sa1的形式。