当前位置: 首页 > article >正文

踩坑记录: 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

尝试解决

  1. 打印模块搜索路径
    当发生问题时, 我的第一反应是这可能类似于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
  1. 改为模块(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

根据上面的结果观察, 应可基本确定这两点:

  1. python中的工作路径(working directory)与模块(module)路径不是一回事.
  2. 工作路径是根据执行命令时所处的实际路径决定的.
    因此像该py文件内的相对路径, 会在我处于根目录下运行文件时失效, 因为此时要寻找的文件路径等效于root/text_file.txt. 这样当然找不到了.

问题解决

既然与路径相关, 那么有而不限于这些方法:

  1. 更改文件路径为绝对路径.
  2. 基于自己的工作路径对文件路径进行修改.
  3. 通过ossys中的方法动态拼接路径, 或动态修改工作路径.

对于我自己而言, 我会根据以下两种情况来分别使用不同方案:

  1. 如果random_dir只是一个文件夹(也是本问题最开始的样子), 它里面的代码独立于根目录下的其它部分. 比如这个根目录其实是一个教学式的内容, 每个文件夹代表不同的章节. 那么我会选择不对文件进行任何修改, 而是直接cd进这个文件夹, 将random_dir当作实际的根目录运行.
  2. 如果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


http://www.kler.cn/a/446675.html

相关文章:

  • Leetcode Hot 100 【二叉树】104. 二叉树的最大深度
  • 部署开源大模型的硬件配置全面指南
  • docker简单命令
  • 深入了解Python模拟负载均衡器:将请求高效分发至多个服务器
  • 电源芯片MPQ2179A(TI)
  • SLAAC如何工作?
  • 基于STM32的自学习智能小车设计
  • 微信小程序实现上传图片自定义水印功能、放大缩小旋转删除、自定义字号颜色位置、图片导出下载、图像预览裁剪、Canvas绘制 开箱即用
  • 【深入理解网络协议】
  • 【学习总结|DAY020】Java FIle、字符集、IO流
  • WPF系列二:窗口模式调整
  • 什么是Edge SCDN?
  • Kibana8.17.0在mac上的安装
  • Midjourney制作APP logo教程
  • Ubuntu20.04 编译运行 ORBSLAM2_with_pointcloud_map(以RGBD Orbbec Astra+为例)保姆级教程
  • Http 中 GET 和 POST 的区别?应用场景都有哪些?
  • imu相机EKF
  • 【数据可视化案例】探索影响不同国家预期寿命的主要因素
  • Flutter:CustomScrollView自定义滚动使用
  • vue2,vue3 中 v-for 和v-if的优先级
  • 30.装饰器
  • 【Ubuntu】安装QQ
  • Apache SeaTunnel 增强对csv读取时分割字段的能力
  • 【论文笔记】欧美学术论文写作习惯解析:非母语作者的指南
  • 仓鼠身长能长到多少厘米?
  • 《第十二部分》1.STM32之RTC实时时钟介绍---BKP实验