本部分不涉及模块搜索方式的具体解释, 有兴趣可以看看我之前的笔记: 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
脚本(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 the current working directory
.sys.path
: A list of strings that specifies the search path for modules
.PYTHONPATH
, 它其实也属于模块搜索路径
的一方: Augment the default search 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
根据上面的结果观察, 应可基本确定
这两点:
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