Python 中 import 引入和 file 寻找的绝对和相对路径
有这样的目录和文件:
foo
├── bar
│ └── main.py
├── img
│ └── benz.png
└── utils
└── tool.py
我们来考察 3 个变量的值:
| where | command | __name__ | sys.path[0] | os.getcwd() |
|---|---|---|---|---|
| in foo/ | python bar/main.py | __main__ | /***/foo/bar | /***/foo |
| in foo/ | python -m bar.main.py | bar.main | /***/foo | /***/foo |
| in foo/bar | python main.py | __main__ | /***/foo/bar | /***/foo/bar |
| in foo/bar | python -m main.py | main | /***/foo/bar | /***/foo/bar |
__name__与执行方式有关:顶层脚本为__main__,-m或从其他 py 文件 import 时是模块名__file__仅与 py 文件有关,永远是/***/foo/bar/main.py,所以就不写入上表了。sys.path系统自动控制,用户可以添加,其中sys.path[0]为顶层脚本文件的目录或模块执行时的 cwdos.getcwd()仅与执行命令时 where 相关
import module 涉及 2 个变量
- sys.path:module 前没有点(
.),则在 sys.path 中寻找,所以必要时 sys.path.append() 添加进去 __name__:module 的相对引用(如:from ..foo import bar)时需要能够在__name__中找到对应个数的点(.),所以只有用 -m 或从其他文件 import 时(即:__name__ != "__main__")才行得通。
常令人困惑的一点还有:VScode 中的 terminal 你 cd 到合适的目录下,但 F5 debug 时 VSCode 会重新 cd 到其他目录,所以你会得到不一样的结果。
所以,如果需要在 main.py 中使用 utils/main.py 中的 object,为了满足 VSCode 中 F5、Terminal 中手动 cd 并执行、及未来被 import 的三种情况,我用这样一段代码来兼容:
pwdpath = os.path.split(os.path.realpath(__file__))[0]
if __name__ == "__main__":
sys.path.append(os.path.realpath(pwdpath + "/../"))
from utils.tool import *
else:
from ..util.tool import *
file 相对路径涉及 1 个变量
如 cv2.imread(file) 中的 file 文件路径,可用绝对和相对,使用相对路径时,相对的 root 涉及:
- os.getcwd():以此为当前目录做基础,找绝对和相对路径,可以用 os.chdir() 来修改,注意 terminal 和 vscode 执行结果不同
同样,VSCode 中 F5 和 Terminal 中的路径不同,导致 file 的根基不同,相对引用变的非常麻烦,比如: 如果在 main.py 中找到 benz.png
cv2.imread('../img/benz.png'): 在 Terminal 中进入 bar 目录下执行 OK,在 Terminal 中进入 foo 目录或 VSCode 打开 foo 目录时 NG。
解决办法有:
os.chdir(sys.path[0])修改当前路径,但 path[0] 仅在顶层脚本中能够如您所想(即:当前文件的路径),但在模块方式时也会败下阵来。- 使用绝对路径:
cv2.imread(os.path.realpath( os.path.join( os.path.split( os.path.realpath(__file__) )[0], '../img/benz.png' )))—— 用当前文件的根目录加相对路径,拼出相对路径再换成绝对路径 —— 确实是完美了,但这么变态的代码谁会用呢?
但其实也不要太纠结,因为 module 或 library 中是不允许出现 file 的,这些应该是留给用户的工作,module 中应该只有算法和机制的实现,所以看开点吧。
