python 绝对导入和相对导入
参考:
https://blog.csdn.net/huaanxiang/article/details/143687649
绝对导入:
标准库导入
假设有 a.py 这么写:
import os
这个是从标准库导入。
当前文件目录导入
如果 a.py 的同级目录下有一个 b.py,b.py 中有 class B,那么在 a.py 也可以导入:
from b import B
这个是从当前文件目录导入,因为运行 a.py,所以当前文件目录就是 a.py 所在目录,这个目录下有 b.py,所以导入成功。
如果嵌套导入,子模块中,当前文件目录也是最开始运行的文件的目录。
可以看下面的例子来理解:
目录树如下
│ a.py
│ a2.py
│
└─b
b.py
a2.py:
class A2():
pass
b.py:
from b.b import A2
a.py:
from b.b import A2
直接运行 python b/b.py
,会失败:
ModuleNotFoundError: No module named 'a2'
但是运行 python a.py
,是会成功的,原因在运行的顶层文件决定了工作目录,工作目录会影响每一个子模块的绝对导入路径。
site-packages 导入:
这个可以理解为导入 conda 子环境里 pip 安装的包。
优先级:
Python 导入模块时,会按以下顺序查找模块:
当前目录:首先,它会检查当前目录(即脚本所在目录)。
标准库目录:如果当前目录没有这个模块,Python 会继续在标准库目录中查找。
site-packages 目录:然后,它会在 site-packages(安装的第三方包)目录中查找。
相对导入:
注意,只要一个 .py 文件使用相对导入方法,你就不能直接运行这个文件,必须是把它当成子模块,在别的文件引用这个模块来调用。
而且,引用的过程中的最顶级目录,必须是一个包(也就是你运行的文件也不能在这个目录里)。
考虑下面的例子:
│ main.py
│
├─a
│ a.py
│
└─c
c.py
main.py:
from a.a import C
a.py:
from ..c.c import C
c.py:
class C:
pass
此时运行 python main.py
,会报错:
ImportError: attempted relative import beyond top-level package>
原因是 a.py 中 .. 到了 ./ 这个目录,但这个目录不是一个包,所以寄,因此,需要把 a 和 c 再打包成一个包,放到 main.py 的同级目录下:
│ main.py
│
└─top
├─a
│ a.py
│
└─c
c.py
main.py 改成:
from top.a.a import C
此时可以运行。
sys.path.append 实现相对导入
相对导入了就不能直接运行文件,这么麻烦,就不是很好写脚本。
假设终端工作目录 ./ 下有两个文件夹:src1 和 src2
src2 里面的一个 .py 文件就是要从 src1 import 东西怎么办呢。
此时可以考虑把当前终端工作目录添加的系统路径里:
src2 中的某个文件
import sys
sys.path.append("./")
然后就可以绝对导入:
from src1.xxx import XXX
或者不在程序里写,运行程序钱,引入环境变量:
export PYTHONPATH=./
但这样要求你的终端工作目录是对的(即 ./ 是 src1 和 src2 的父目录),
注:也可以把 ./ 换成绝对目录,但这样迁移环境很麻烦。
很麻烦,怎么用 sys 实现相对呢?用 os 获得一下就行了:
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
init.py
我们发现上面的 import 都是 a.a ,第一个 a 是文件夹名,第二个 a 是文件名,很不方便
如果想直接 from a import XXX
,并且这个 a 指的就是文件夹名,就需要 init.py
a 文件夹下建一个 init.py,然后:
from .a import XXX
这样就可以了,可以理解为 init.py 在帮这个文件夹 import。