Skip to main content

Python 中 import 引入和 file 寻找的绝对和相对路径

wKevin
一颗向上的水滴

有这样的目录和文件:

foo
├── bar
│   └── main.py
├── img
│   └── benz.png
└── utils
└── tool.py

我们来考察 3 个变量的值:

wherecommand__name__sys.path[0]os.getcwd()
in foo/python bar/main.py__main__/***/foo/bar/***/foo
in foo/python -m bar.main.pybar.main/***/foo/***/foo
in foo/barpython main.py__main__/***/foo/bar/***/foo/bar
in foo/barpython -m main.pymain/***/foo/bar/***/foo/bar
  • __name__ 与执行方式有关:顶层脚本为 __main__-m或从其他 py 文件 import 时是模块名
  • __file__ 仅与 py 文件有关,永远是 /***/foo/bar/main.py,所以就不写入上表了。
  • sys.path 系统自动控制,用户可以添加,其中 sys.path[0] 为顶层脚本文件的目录或模块执行时的 cwd
  • os.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。

解决办法有:

  1. os.chdir(sys.path[0]) 修改当前路径,但 path[0] 仅在顶层脚本中能够如您所想(即:当前文件的路径),但在模块方式时也会败下阵来。
  2. 使用绝对路径: cv2.imread(os.path.realpath( os.path.join( os.path.split( os.path.realpath(__file__) )[0], '../img/benz.png' ))) —— 用当前文件的根目录加相对路径,拼出相对路径再换成绝对路径 —— 确实是完美了,但这么变态的代码谁会用呢?

但其实也不要太纠结,因为 module 或 library 中是不允许出现 file 的,这些应该是留给用户的工作,module 中应该只有算法和机制的实现,所以看开点吧。