Part 1
我们先上案例,再分析原因。
若在pycharm新建工程,再创立几个文件,文件结构如下图
也就是说,我们在工程下有文件test.py
和文件夹p
,在p下分别有run.py
和tool.py
两个文件
一开始,py
文件中都为空,现在一步一步来
先在test
文件中写入import p.run
,然后运行test
文件,发现程序正常运行;
我们再在run
文件中写入import tool
,运行run
,发现依然正常。
我们再回到test
文件,运行,结果如下:
python">ModuleNotFoundError: No module named 'tool'
系统无法找到tool
模块,这是为什么?
因为我们平时运行import
时,是没有指明路径的,此时系统会从sys.path
中寻找路径,来依次查看每一个路径中是否有我们需要的模块,我们修改test文件如下,并运行
python"># import p.run
import sys
print(sys.path)
结果为:
python">['D:\\code\\pythonProjectTest', 'D:\\code\\pythonProjectTest', 'D:\\Python\\Python39\\Lib\\site-packages', 'D:\\Python\\Python39\\python39.zip', 'D:\\Python\\Python39\\DLLs', 'D:\\Python\\Python39\\lib', 'D:\\Python\\Python39', 'D:\\code\\pythonProjectTest\\venv', 'D:\\code\\pythonProjectTest\\venv\\lib\\site-packages']
我们再来到`run模块,执行如下代码:
python"># import tool
import sys
print(sys.path)
有结果:
python">['D:\\code\\pythonProjectTest\\p', 'D:\\code\\pythonProjectTest', 'D:\\Python\\Python39\\Lib\\site-packages', 'D:\\Python\\Python39\\python39.zip', 'D:\\Python\\Python39\\DLLs', 'D:\\Python\\Python39\\lib', 'D:\\Python\\Python39', 'D:\\code\\pythonProjectTest\\venv', 'D:\\code\\pythonProjectTest\\venv\\lib\\site-packages']
系统检索sys.path
的机制和windows
中的PATH
是类似的,因此不再解释
我们只看sys.path
的前2项,其中第一项是当前py
文件所属文件夹,第二项是工程文件夹。而一旦执行了某个py
文件,sys.path
就已经确定了
因此原因就出来了,我们之前执行test
文件时,生成的sys.path
不会含有'D:\\code\\pythonProjectTest\\p'
路径,自然就找不到模块tool
了
Part 2
我们依然一边运行,一边分析
之前无法在run
中导入tool
,现在我们把run
中代码改为from . import tool
,在回到test
执行代码,会发现程序正常运行。
原因:我们采用了相对路径而不是绝对路径,.
代表所属上一级目录。此外,..
代表上上级目录
这里需要额外说明的是,不能像在命令行中把.
和..
完全理解为针对目录的操作,因为在python中,上一级和上上级是针对模块名称进行操作的。也许说得有点抽象,我们上代码:在test
和run
中,分别在第二行加入语句print(__name__)
,然后在test
中执行,有:
python">p.run
__main__
其中run
中的语句先打印,这一点大家应该明白。
也就是说,每个模块都有一个__name__
属性,若某模块直接以脚本的方式被执行,那么模块名称就是__main__
,而若使用模块的方式被加载,模块名就会对应地改变
之后,run
模块的模块名称就是p.run
,p
就是它的顶级名称(类似目录),也就是在run
中能够访问到的最高级。
比如我们在test
同级处新建文件test2
,改run
中代码为from .. import test2
,之后来到test
并执行,有结果
python">ImportError: attempted relative import beyond top-level package
即超越了顶级包的位置,不能访问。同理,在脚本文件中也不能使用.
的方式来访问属于同一层级的其他模块,因为包名__main__
中并没有层级结构
以上适用于py3,py2有所不同,但使用越来越少,不再说明了。