踩坑记录: Python的工作路径(working dircetory)
本部分不涉及模块搜索方式的具体解释, 有兴趣可以看看我之前的笔记: Python中令人困惑的模块导入.
问题描述
项目简介
首先给出一个简单的项目结构:
root
└── random_dir
├── random_file.py
└── text_file.txt
root
是项目的根目录, 旗下只有一个名为random_dir
的文件夹, 在这个文件夹内, 有一个python的脚本文件random_file.py
, 以及一份空的文本文件text_file.txt
.
其中random_file.py
的功能很简单, 检测同一路径下的text_file.txt
文件是否存在, 并输出相应信息, 内部代码为:
# root/random_dir/random_file.py
import os
def main() -> None:
if os.path.exists(r'./text_file.txt'):
print('===>file found\n')
else:
print('===>file NOT found\n')
if __name__ == '__main__':
main()
问题复现
- 当我处于
random_dir
下运行时, 一切顺利:
(default) neowell@RangerSequoia: ~/Python/root/random_dir
$ python random_file.py
===>file found
- 但当我在根目录下, 同样按
脚本
方式运行时, 发生报错:
(default) neowell@RangerSequoia: ~/Python/root/random_dir
$ cd ..
(default) neowell@RangerSequoia: ~/Python/root
$ python random_dir/random_file.py
===>file NOT found
尝试解决
- 打印模块搜索路径
当发生问题时, 我的第一反应是这可能类似于python的模块搜索路径问题, 但这两种运行方式都属于脚本(script)
运行, 它们的模块路径都是一致的. 不论如何, 我决定先打印出来看看, 于是对random_file.py
增加了一点代码:
# root/random_dir/random_file.py
import os
import sys
def main() -> None:
module_path: str = sys.path[0] # 第一个变量为基于项目的模块搜索路径
print(f'当前模块的搜索路径: {module_path}\n')
if os.path.exists(r'./text_file.txt'):
print('===>file found\n')
else:
print('===>file NOT found\n')
if __name__ == '__main__':
main()
两次运行的结果如下, 两种方式如预期, 都是一样的模块路径:
(default) neowell@RangerSequoia: ~/Python/root
$ python random_dir/random_file.py
当前模块的搜索路径: /Users/neowell/Python/root/random_dir
===>file NOT found
(default) neowell@RangerSequoia: ~/Python/root
$ cd random_dir
(default) neowell@RangerSequoia: ~/Python/root/random_dir
$ python random_file.py
当前模块的搜索路径: /Users/neowell/Python/root/random_dir
===>file found
- 改为
模块(module)
的方式来运行
根据上面的结果, 看来和脚本
运行方式无关, 那么用模块
的方式进行呢, 我也试了下, 为此, 需要将random_dir
文件夹变为一个python模块
:
(default) neowell@RangerSequoia: ~/Python/root
$ ls random_dir
random_file.py text_file.txt
(default) neowell@RangerSequoia: ~/Python/root
$ touch random_dir/__init__.py
(default) neowell@RangerSequoia: ~/Python/root
$ python -m random_dir.random_file
当前模块的搜索路径: /Users/neowell/Python/root
===>file NOT found
(default) neowell@RangerSequoia: ~/Python/root
$ cd random_dir
(default) neowell@RangerSequoia: ~/Python/root/random_dir
$ python -m random_file
当前模块的搜索路径: /Users/neowell/Python/root/random_dir
===>file found
由此看来, 找不到文件的原因和模块搜索路径
应该无关, 应该是由其它因素来控制文件的搜寻路径.
问题原因
在random_file.py
中, 文件路径使用的是相对路径
, 当我处于根目录下时就会使寻找文件失效, 而这个现象和上一节的模块搜寻路径显然是互相独立关系, 所以我想到了对于文件路径, 应该是取决于我执行命令时所处的路径位置
.
这个路径被称作工作路径(working directory)
, 可以通过os.getcwd()
进行查看. 如果我们看文档中对它和sys.path
的描述, 就会发现不一样的地方:
os.getcwd
: Return a string representing thecurrent working directory
.sys.path
: A list of strings that specifies thesearch path for modules
.- 包括
PYTHONPATH
, 它其实也属于模块搜索路径
的一方: Augment the defaultsearch path for module files
.
接下来再加一点代码, 把工作路径也打印出来看看:
# root/random_dir/random_file.py
import os
import sys
def main() -> None:
module_path: str = sys.path[0] # 第一个变量为基于项目的模块搜索路径
print(f'当前模块的搜索路径: {module_path}\n')
print(f'当前的工作路径: {os.getcwd()}\n')
if os.path.exists(r'./text_file.txt'):
print('===>file found\n')
else:
print('===>file NOT found\n')
if __name__ == '__main__':
main()
不同行为的测试结果:
(default) neowell@RangerSequoia: ~/Python/root/random_dir
$ python random_file.py
当前模块的搜索路径: /Users/neowell/Python/root/random_dir
当前的工作路径: /Users/neowell/Python/root/random_dir
===>file found
(default) neowell@RangerSequoia: ~/Python/root/random_dir
$ cd ..
(default) neowell@RangerSequoia: ~/Python/root
$ python random_dir/random_file.py
当前模块的搜索路径: /Users/neowell/Python/root/random_dir
当前的工作路径: /Users/neowell/Python/root
===>file NOT found
(default) neowell@RangerSequoia: ~/Python/root
$ python -m random_dir.random_file
当前模块的搜索路径: /Users/neowell/Python/root
当前的工作路径: /Users/neowell/Python/root
===>file NOT found
根据上面的结果观察, 应可基本确定
这两点:
- python中的工作路径(working directory)与模块(module)路径不是一回事.
- 工作路径是根据执行命令时所处的实际路径决定的.
因此像该py文件内的相对路径, 会在我处于根目录下运行文件时失效, 因为此时要寻找的文件路径等效于root/text_file.txt
. 这样当然找不到了.
问题解决
既然与路径相关, 那么有而不限于这些方法:
- 更改文件路径为绝对路径.
- 基于自己的工作路径对文件路径进行修改.
- 通过
os
与sys
中的方法动态拼接路径, 或动态修改工作路径.
对于我自己而言, 我会根据以下两种情况来分别使用不同方案:
- 如果
random_dir
只是一个文件夹(也是本问题最开始的样子), 它里面的代码独立于根目录下的其它部分. 比如这个根目录其实是一个教学式的内容, 每个文件夹代表不同的章节. 那么我会选择不对文件进行任何修改, 而是直接cd
进这个文件夹, 将random_dir
当作实际的根目录运行. - 如果
random_dir
是属于该项目的一个模块, 那么我会将其与模块搜索路径
的逻辑进行统一, 以根目录root
作为前提来设置相应的路径位置. 为此我可会调整file_a.txt
的实际存放位置, 或者random_file.py
中的路径值. 即一切配合python -m xx.yy
的运行形式进行路径配置.
最后
在之前好不容易折腾清楚了python的模块导入逻辑后, 居然踩中了另一个不大不小的坑. 需要说明的是, 针对工作路径的定义仅根据os.getcwd
的文档说明, 和我的观察所得, 我没能在python或其它文档中找到确定的定义working dirctory的描述, 如果有人能找到相关的确定来源, 请务必告诉我, 感谢.
参考
[1] https://docs.python.org/3/library/sys.html#sys.path
[2] https://docs.python.org/3.10/using/cmdline.html#envvar-PYTHONPATH
[3] https://docs.python.org/3.13/library/os.html#os-file-dir