news 2026/10/29 5:36:13

Python路径陷阱:分清os.getcwd()与__file__的正确用法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python路径陷阱:分清os.getcwd()与__file__的正确用法

做 Python 项目做久了,总会遇到那种"本地跑得好好的,一部署就报 FileNotFoundError"的诡异问题。九成情况下,根子在路径上,再往下追一层,就是程序员把"当前工作目录"和"当前脚本所在目录"当成了同一个东西。这两个概念在 Python 里分别由os.getcwd()和__file__这一族 API 来回答,它们回答的是两个完全不同的问题,用错一个,代码在开发机上可能依然能跑通,换个启动方式立刻崩。这篇把获取当前文件目录的常见方法全部拆开讲清楚:每种写法到底返回什么、在什么场景下会失真、性能差多少、打包分发之后又会变成什么样。不管你是刚写完第一个脚本的新手,还是维护着几十个模块的老手,这里面的坑大概率你都踩过或者即将踩到。

1. 两个"当前目录"的语义分野,决定了你该用哪个 API

很多人第一次写文件读取,是从 Stack Overflow 上抄来一句open('config.ini'),发现本地能跑,就认为路径问题解决了。等代码进了定时任务或者被别的东西调用,立刻崩溃。这不是运气问题,而是因为 Python 里同时存在两个"当前位置"的概念,而且它们经常不相等。

1.1 os.getcwd() 回答的是"进程在哪"

os.getcwd()返回的是当前进程的工作目录(current working directory,简称 cwd)。这个值由启动这个进程的父进程决定,跟你的代码文件放在哪个文件夹里毫无关系。你在/home/xxx/deploy下敲python /home/xxx/code/main.py,那么os.getcwd()返回的就是/home/xxx/deploy,哪怕main.py在/home/xxx/code里躺着。

这个特性其实是从 Unix 继承下来的,本质上 cwd 是进程的一个属性,os.chdir()可以随时改它,改完之后所有相对路径的解析基准都跟着变。所以os.getcwd()只有在一种情况下是可靠的:你明确知道自己需要"用户当前在哪个目录下操作",比如写一个命令行工具,用户cd到某个项目目录里执行mytool init,你希望配置文件生成在他当前所在的目录,这时候用 cwd 完全正确,甚至必须用 cwd。

但如果你要读的是"跟我这个脚本放在一起的资源文件",用 cwd 就是纯粹的赌博。赌的是调用者恰好在你预期的目录里启动了进程。这个赌注在本地开发时胜率很高,因为大家习惯在项目根目录下敲命令,所以问题被掩盖了;一旦上线,启动方式换成 systemd、计划任务、IDE 的运行按钮、容器里的 entrypoint,胜率立刻归零。

1.2file回答的是"代码在哪"

__file__是模块对象的一个属性,指向这个模块对应的源文件路径。它回答的是"这段代码本身存放在哪里",跟进程当前在哪没有关系。所以想定位跟脚本同目录的配置文件、模板、静态资源,正确的锚点永远是__file__而不是 cwd。

这里有个容易忽略的细节:__file__的值不保证是绝对路径。在 Python 3.8 及更早的版本里,如果你用相对路径启动脚本(比如python main.py),__file__拿到的可能就是main.py这样一个光秃秃的相对名;从 3.9 开始官方把它规范化成了绝对路径,但如果你是通过某些特殊方式加载模块(比如runpy、自定义 loader),它仍然可能是相对路径或者带..的路径。这就是为什么所有靠谱的写法都要在外面套一层os.path.abspath()或者Path.resolve()。

我把这两个概念的区别总结成一张表,遇到路径问题时先对着表确认自己要的是哪一列:

维度os.getcwd()__file__派生路径
语义进程当前工作目录当前模块源文件所在目录
由谁决定启动进程的父进程 /os.chdir()模块在文件系统中的实际位置
是否随chdir变化会变不会变
本地开发是否可靠通常可靠始终可靠
部署后是否可靠极易失效可靠(打包场景除外)
典型正确用途CLI 工具按用户位置读写文件加载随包分发的资源文件

1.3 为什么混淆这两个概念会在上线后才爆雷

我见过一个特别典型的案例。某团队写了一个数据清洗脚本,里面用open('mapping.json')读取一张映射表,映射表和脚本放在同一个目录。开发阶段大家都习惯cd到那个目录再执行python clean.py,一年多没出过问题。后来这个脚本被接入一个调度平台,平台的工作方式是先把脚本复制到临时目录,然后在自己的运行目录里exec它,结果第一天就全线报错——因为 cwd 变成了平台的运行目录,而mapping.json根本不在那儿。

这类问题的隐蔽性在于:错误不会在写代码的那一刻暴露,而是在环境变化的那一刻暴露。而环境变化往往发生在部署、迁移、容器化、接入新平台这些节点上,正好是最不希望出事的时候。所以判断标准很简单:只要你要访问的文件是"跟着代码一起走的",就必须用__file__派生路径;只有当你需要访问的文件是"跟着用户操作走的",才用 cwd。这条线划清楚,后面所有 API 的选择都是它的推论。

提示:写代码时如果一时分不清,可以问自己一句"如果把这个脚本复制到别的目录执行,我希望它读写哪个位置的文件"。答案指向代码旁边就用__file__,指向执行位置就用 cwd。

2. os.getcwd() 的真实行为边界与三次踩坑记录

os.getcwd()本身没什么复杂度,一行调用返回一个字符串,但它背后牵连的是整个进程的路径解析基准,副作用面比想象中大。这一节把它的行为边界讲透,包括那些只有踩过才知道的细节。

2.1 启动位置决定一切:从命令行、IDE 到计划任务的差异

同一个脚本,用不同方式启动,os.getcwd()可能给出完全不同的结果。下面这张表是我在实际项目里逐一验证过的:

启动方式cwd 的实际值
终端里cd /project && python main.py/project
终端里python /project/sub/main.py你敲命令时所在的目录
某 IDE 的运行按钮(默认配置)通常是项目根目录或脚本所在目录,可在运行配置里改
systemd 服务(未设置 WorkingDirectory)/(根目录)
计划任务(未设置起始于)各平台的默认目录,通常是用户主目录或系统目录
容器 entrypointWORKDIR指令指定的目录
双击运行打包后的可执行文件可执行文件所在目录,或快捷方式的"起始位置"

注意 systemd 那一行,默认 cwd 是/,这个坑很多人踩过。服务里用相对路径读配置,本地测试全过,一上服务器就找不到文件,日志里只有一行孤零零的FileNotFoundError: config.yaml。修法有两种,一是在 unit 文件里补WorkingDirectory=/path/to/app,二是把代码改成用__file__定位。我更推荐后者,因为前者只是把隐式依赖换了个地方写,换个部署方式又得改一遍。

某次我在一个容器化项目里还遇到过更绕的情况:镜像里WORKDIR设成了/app,但 entrypoint 脚本先cd到/data再启动主程序,结果主程序里的 cwd 是/data。当时排查了半天,最后是在启动日志里把os.getcwd()打出来才定位到。从那以后我养成了一个习惯:任何跟路径相关的诡异问题,第一件事就是把os.getcwd()和__file__都打印出来,一眼就能看出是不是基准搞错了。

2.2 os.chdir() 的全局副作用

os.chdir()是修改进程工作目录的唯一途径,它的影响范围是整个进程,不是当前函数、当前线程。这意味着你在某个工具函数里随手os.chdir(target),之后进程里所有还在用相对路径的代码——包括第三方库内部的实现——解析基准全部变了。

这个副作用在多线程环境下尤其危险。Python 的 cwd 是进程级的,线程 A 调了chdir,线程 B 里正在执行的open('log.txt')立刻指向了新目录。这种 bug 极难复现和定位,因为出问题的代码和肇事代码可能相隔十万八千里。

我的建议很直接:业务代码里尽量不写os.chdir()。确实需要临时切目录的场景(比如调用一个只接受相对路径的外部命令),用上下文管理器把它包起来,进入时记住原目录,退出时立刻切回来:

import os from contextlib import contextmanager @contextmanager def working_dir(path): old = os.getcwd() os.chdir(path) try: yield finally: os.chdir(old)

这样至少保证了异常路径下也能还原。但即便如此,多线程场景下它依然不安全,因为切回去的瞬间别的线程可能已经用错误的基准打开过文件了。真正干净的做法是用subprocess的cwd=参数把目录切换限制在子进程范围内,主进程完全不受影响:

import subprocess subprocess.run( ["some-tool", "--input", "data.txt"], cwd="/path/to/workdir", check=True, )

这行代码里,/path/to/workdir只对子进程生效,主进程的 cwd 纹丝不动。参数一换,问题就隔离了,这是我个人最推荐的替代方案。

2.3 什么时候用 cwd 才是对的

说完风险,也得说清楚 cwd 不可替代的地方,否则容易矫枉过正。以下三类场景,用 cwd 是设计上的正确选择:

第一类是CLI 工具的输入输出。用户在某目录下执行mytool build,你希望产物生成在他当前所在的位置,这时候读 cwd 是符合用户直觉的。硬用__file__反而会把产物扔到工具安装目录里去,那是错的。

第二类是遵循配置文件查找惯例。不少工具会按"当前目录 → 用户主目录 → 系统配置目录"的顺序逐级查找配置,这个链条的起点天然就是 cwd。

第三类是交互式脚本和临时分析。写个一次性脚本处理当前目录下的一批 CSV,用glob.glob('*.csv')就完事了,没必要上纲上线。这种代码的生命周期本来就短,可读性比健壮性重要。

判断的核心还是那句:这个位置是用户给的还是代码给的。用户给的用 cwd,代码给的用__file__。把这条原则记牢,就不用在每个项目里重新纠结一遍。

3.file三条技术路线:abspath、realpath 与 pathlib

确定要用__file__之后,接下来的问题是"怎么把它变成目录"。这件事在 Python 里有至少三种主流写法,它们在绝大多数情况下结果相同,但在软链接、..路径、跨平台分隔符这些边界条件下会出现分歧。

3.1 经典老写法 os.path.dirname(os.path.abspath(file))

这是流传最广的写法,几乎是 Python 社区的一种肌肉记忆:

import os BASE_DIR = os.path.dirname(os.path.abspath(__file__))

拆开看两步。os.path.abspath(__file__)做了两件事:如果传入的是相对路径,用os.getcwd()作为基准拼成绝对路径;然后规范化掉路径里的.和多余的分隔符。注意它不会处理..(在大多数平台上abspath内部会调用normpath,实际上会折叠..,但折叠是纯字符串操作,不验证目录是否真实存在),也不会解析软链接。

然后是os.path.dirname(),纯粹砍掉最后一段,得到父目录。整体效果就是"这个脚本文件所在目录的绝对路径"。

这个写法的优点是兼容性极好,Python 2 时代就能用,任何环境都跑得通。缺点是它依赖 cwd 来补全相对路径——如果__file__恰好是相对路径,而进程在执行到这一行之前调用过os.chdir(),那么abspath拼出来的结果就是错的。虽然 Python 3.9+ 基本不会出现相对__file__了,但在老版本或者特殊加载器下仍然可能遇到。所以严格来说,这个写法有一个微小的理论缺陷,实践中出问题的概率很低,但不是零。

3.2 realpath 与 abspath 在软链接下的分道扬镳

os.path.realpath()比abspath多做一件事:解析路径上的所有软链接,返回真正的物理路径。它们的差异只在一种情况下体现——你的脚本文件或者它所在的目录是一个软链接。

假设部署时用了这样的结构:真实代码在/opt/releases/v1.2.3/app/,同时有一个软链接/opt/current -> /opt/releases/v1.2.3/app,服务通过/opt/current/main.py启动。这时候:

  • abspath得到/opt/current/main.py,dirname之后是/opt/current
  • realpath得到/opt/releases/v1.2.3/app/main.py,dirname之后是/opt/releases/v1.2.3/app

哪个才是你要的?取决于资源文件放在哪。如果资源跟着代码一起发布在 releases 目录里,realpath是对的,因为从/opt/current读也能读到,但如果你要做版本相关的逻辑(比如记录当前运行的版本号),realpath能拿到真实版本。反过来,如果你希望日志、上传文件这些写到/opt/current/下面,跟着软链接切换自动指向新版本,那abspath更符合预期。

这就是所谓的"蓝绿部署路径陷阱"。我在一个项目里见过因为用了realpath,导致程序把用户上传的文件写进了带版本号的目录,回滚版本之后文件全"消失"了——其实还在旧版本的目录里躺着。后来改成基于abspath(也就是基于软链接入口的路径)来定位可写目录,问题才解决。

我的经验是:只读的代码资源用realpath,可写的运行时数据用abspath或者干脆用配置项显式指定。这个区分不写下来很容易忘,建议直接在代码注释里标一句。

3.3 pathlib 版本的等价写法与 3.6+ 的 strict 陷阱

Python 3.4 引入pathlib之后,越来越多的项目改用面向对象的路径操作:

from pathlib import Path BASE_DIR = Path(__file__).resolve().parent

这行等价于realpath+dirname,因为resolve()默认就会解析软链接。可读性比嵌套的函数调用好太多,链式表达一目了然。

但resolve()有一个历史包袱必须提。在Python 3.6 之前,resolve()是严格模式,路径中任何一段不存在就会抛FileNotFoundError;从3.6 开始,它变成了非严格模式,不存在的部分原样保留,不再抛异常,同时新增了strict参数。如果你维护的代码需要兼容 3.5 及更早版本(现在应该很少了),得写成resolve(strict=False)之外的形式,或者干脆退回去用os.path。

反过来,如果你确实希望路径必须存在才继续,那就显式写resolve(strict=True),让它在文件缺失时直接报错,比在后续open()时才炸要清晰得多。这个用法我在加载一些必须存在的资源文件时会用,相当于把校验提前了。

还有一个细节:Path(__file__).parent和Path(__file__).resolve().parent在__file__本身是绝对路径且没有软链接时结果完全一样,所以很多简单脚本直接写.parent也没问题。但一旦涉及软链接或者相对路径,差异就出来了。我的默认习惯是一律加.resolve(),多打九个字符,换一个不用思考边界条件的心安。

3.4 性能与可读性的实测对比

有人担心pathlib比os.path慢,这个担心在模块加载时计算一次的场景下完全没必要,但既然聊到了,还是给个量级参考。在我的机器上跑一百万次调用,大致是这样的数量级:

写法单次耗时(微秒级)相对开销
os.path.dirname(os.path.abspath(__file__))约 1.21x
os.path.dirname(os.path.realpath(__file__))约 4.5约 3.7x
Path(__file__).resolve().parent约 5.0约 4x

数字仅供参考,不同平台和 Python 版本会有波动,但结论稳定:realpath和resolve因为有文件系统查询(要解析软链接),开销明显更高。然而在真实项目里,这些计算通常发生在模块顶层,一个进程只跑一次,五微秒和二十微秒的差别在人类感知之外。只有在循环里反复计算路径时,才需要把它们提到循环外面缓存起来,这也算是基本常识。

所以我最终的选型建议是按场景分:新项目一律用pathlib,可读性收益远大于那几微秒;需要兼容老版本或者和老代码风格统一的,用os.path.abspath那一套;涉及软链接语义的,明确写下用realpath还是abspath,并加注释说明原因。

4.file不存在的四种场景与各自的兜底策略

前面所有的讨论都建立在一个前提上:__file__存在。但这个前提在不少场景下会直接崩塌,最典型的就是抛NameError: name '__file__' is not defined。这一节把这几种场景逐一拆开,给出各自的处理方式。

4.1 交互式解释器与 python -c

在 REPL 里敲代码,或者用python -c "..."执行一段字符串,压根没有"源文件"这个概念,自然也不会有__file__。这时候正确的锚点是os.getcwd(),因为用户就是在当前目录下操作,用 cwd 完全合理:

import os try: BASE_DIR = os.path.dirname(os.path.abspath(__file__)) except NameError: BASE_DIR = os.getcwd()

这个 try/except 模式非常常见,基本可以当作标准写法。但要注意,它只能覆盖"没有__file__"这一种情况,后面讲的几种场景会以别的方式失效,光靠这一段兜不住。

4.2 Jupyter Notebook 的file是假的

在 Notebook 里,__file__压根不是你想的那个东西。早期的 IPython 里访问它会抛NameError,后来的版本给了一个值,但那个值是内核启动时的工作目录拼上一个大致的文件名,跟.ipynb文件实际存放的位置没有关系。如果你把 notebook 挪到别的目录,它依然返回老的路径。

所以判断逻辑要加一层:

def get_base_dir(): try: # Notebook 环境下 __file__ 不可信 if "ipykernel" in sys.modules or get_ipython().__class__.__name__ == "ZMQInteractiveShell": return Path.cwd() except NameError: pass return Path(__file__).resolve().parent

更稳的做法是用IPython提供的钩子:某些版本里可以通过get_ipython().starting_dir拿到 notebook 打开时所在的目录,但那也只是"启动目录"而非"notebook 文件所在目录"。想要拿到 notebook 文件本身的路径,得走内核的连接信息,非常麻烦。我一般的处理是:Notebook 里一律用Path.cwd(),并且把 notebook 放在项目根目录打开,用约定来规避这个问题,比写一堆探测逻辑实在。

4.3 exec / eval 加载的代码

用exec(open('plugin.py').read())这种方式加载的代码,__file__不会被自动设置。可以手动注入一个:

namespace = {"__file__": os.path.abspath("plugin.py")} exec(compile(open("plugin.py").read(), "plugin.py", "exec"), namespace)

注意compile的第二个参数filename,它决定了 traceback 里显示的文件名,也影响__file__的可见性。用exec三参数形式加上命名空间注入,是插件系统里最常见的处理手法。如果你用importlib加载,情况会好一些,spec_from_file_location会正确设置__file__。

我在一个插件式项目里被这个问题坑过一次:插件通过exec加载,插件内部用__file__找自己的模板文件,结果直接 NameError。当时的修法就是在调用方统一注入__file__,并在插件开发文档里写明"你可以直接用__file__,我们会替你设置好"。这个约定比让每个插件自己处理要省心得多。

4.4 python -m 与 sys.argv[0] 的差异

用python -m package.module启动时,__file__是正常的,但sys.argv[0]会变成模块的路径表示(比如/path/to/package/module.py或者-m相关的形式),跟python module.py直接启动时不一样。

如果你在写一个需要自举的命令行工具,可能会考虑用sys.argv[0]反推脚本目录。这个思路在某些场景下可行,但坑很多:sys.argv[0]可能被调用方改写,可能只是一个名字而没有路径(通过PATH查找启动时),冻结构建后可能是可执行文件路径。相比之下__file__稳定得多。

我的建议是:sys.argv[0]只用来做"程序叫什么名字"这种展示用途,路径定位一律走__file__。如果确实需要拿到入口脚本的真实路径,用importlib的老 API 或者干脆在入口模块里定义常量,比反推可靠。

5. 打包与分发后的路径:单文件程序、zipapp 与被 import 的库

本地开发跑通只是第一步,代码打包分发出去之后,__file__的语义会发生变化。这一节讲三种常见分发形态下的路径处理。

5.1 打包成单文件可执行程序后的 sys.frozen 判断

用打包工具把 Python 程序打成单文件 exe 之后,运行时资源会被解压到一个临时目录,此时__file__指向的是那个临时目录里的模块路径,而不是用户看到的 exe 所在位置。这意味着两件事:读取随包资源是可行的(资源也解压在同一个临时目录),但写文件绝对不能往那个目录写,进程退出后就没了。

判断当前是否处于打包环境,通行的做法是检查sys.frozen:

import sys from pathlib import Path def app_root() -> Path: if getattr(sys, "frozen", False): # 打包后的可执行文件所在目录 return Path(sys.executable).resolve().parent return Path(__file__).resolve().parent

如果想拿到"打包进去的资源文件"所在目录,各打包工具提供的属性不同,常见的是sys._MEIPASS(部分工具会设置)或者__file__所在的临时目录。这个差异必须查对应工具的文档,没有跨工具的统一标准。我一般会封装一个resource_path(relative)函数,把所有工具差异收在这一个函数里,业务代码只调这个函数:

def resource_path(relative: str) -> Path: base = getattr(sys, "_MEIPASS", None) if base: return Path(base) / relative return Path(__file__).resolve().parent / relative

这个函数的语义是"读取随包分发的只读资源"。而可写位置(日志、缓存、用户数据)应该走操作系统的标准目录,比如用Path.home() / ".myapp",或者更规范地走平台相关的应用数据目录。把只读资源和可写数据分开定位,是避免打包后各种诡异问题最有效的一条规矩。

5.2 zipapp 里file指向压缩包内部

把程序打包成.pyz之后,模块的__file__会变成类似/path/to/app.pyz/module.py的形式——注意这个路径在文件系统里并不存在,app.pyz是一个文件,不是一个目录。这时候Path(__file__).resolve()会得到一个畸形的结果,os.path.exists返回 False。

在这种形态下访问资源,正确的方式是通过 zip 读取接口,或者干脆用importlib.resources:

from importlib.resources import files config_text = files("mypackage").joinpath("config.json").read_text(encoding="utf-8")

importlib.resources是 Python 3.7 引入的标准方案,从 3.9 开始提供了files()这个返回Traversable对象的接口,能同时处理普通目录、zip 包和打包后可执行程序里的资源。如果你的项目需要支持多种分发形态,把这个 API 用起来能省掉大量条件判断。

5.3 作为库被 import 时,为什么该优先用 importlib.resources

一个容易被忽略的事实:当你的包被别人pip install之后,__file__指向的是 site-packages 里的安装目录。读资源本身是没问题的,但有几个隐患:用户可能用--user安装、可能在虚拟环境里、可能在只读的文件系统里(比如某些受限容器),而你用__file__拼出来的路径去写文件就会失败。

更强的约束来自打包格式。某些部署工具会把 Python 包打成压缩格式加载,这时候__file__又不可靠了。importlib.resources就是为这个问题设计的抽象层,它不假设资源在文件系统上的具体位置,只保证你能读到内容。

所以对于库作者,我的建议是分级处理:只读资源一律走importlib.resources;需要路径对象传给第三方库的情况,用files(...)配合上下文管理器拿到临时文件路径;可写数据一律由调用方通过参数传入。最后这条尤其重要,一个库如果自己决定往哪写数据,几乎必然会和某个用户的部署环境冲突。

6. 选型决策表与我的默认写法模板

讲完所有分支,最后落地到工程实践:实际写代码时怎么选、怎么封装、怎么测试。

6.1 需求到 API 的对照表

我把这些年遇到的需求归成几类,直接对照查表最快:

需求推荐写法关键理由
读同目录的配置文件Path(__file__).resolve().parent / "config.yaml"与启动位置无关
用户当前目录下创建产物Path.cwd()符合 CLI 直觉
读随包分发的静态资源importlib.resources.files(...)兼容压缩包与打包形态
打包后可写的数据目录Path.home() / ".myapp"避开只读安装目录
需要在软链接下跟随真实版本Path(__file__).resolve().parent解析软链接
需要保持软链接入口语义os.path.dirname(os.path.abspath(__file__))不解析软链接
插件/exec 加载的代码调用方注入__file__否则 NameError

查表的时候有一个常见误判:看到"配置文件"四个字就直接选第一行,但有些配置文件其实是用户提供的(比如放在当前目录的.myapprc),那种情况该走第二行。区分标准还是"代码给的还是用户给的"。

6.2 我项目里常驻的三个工具函数

每个新项目我都会先建一个paths.py,放三个函数,后面所有模块都从这里取路径,不在业务代码里散落__file__:

# paths.py import sys from pathlib import Path def project_root() -> Path: """项目根目录:当前文件的上两级""" return Path(__file__).resolve().parent.parent def script_dir() -> Path: """当前脚本所在目录""" return Path(__file__).resolve().parent def data_dir(name: str = "") -> Path: """可写数据目录,自动创建""" base = Path.home() / ".myapp" / "data" target = base / name if name else base target.mkdir(parents=True, exist_ok=True) return target

这套封装的收益不在于代码量,而在于路径的来源被收敛到了一个地方。将来要改目录结构、要支持环境变量覆盖、要加打包判断,都只改这一个文件。分散在各处的os.path.dirname(os.path.abspath(__file__))是维护的噩梦,改一次得全局搜索替换,很容易漏。

data_dir里用了parents=True, exist_ok=True,这两个参数是mkdir必带的。少了parents=True,父目录不存在就抛异常;少了exist_ok=True,目录已存在也抛异常。这两行写全,能省掉大量if not path.exists()的判断。

6.3 单元测试里怎么验证路径正确性

路径代码最大的问题是"本地测不出来"。我的做法是写一个专门验证路径函数的测试,核心思路是切换 cwd 后检查结果不变:

import os from pathlib import Path def test_project_root_stable_across_cwd(tmp_path, monkeypatch): before = project_root() monkeypatch.chdir(tmp_path) after = project_root() assert before == after assert before.is_absolute()

这个测试的价值在于它把"不依赖 cwd"这个隐含契约显式化了。以后有人为了省事把实现改成Path.cwd(),测试立刻变红。同理,如果要验证软链接行为,可以在tmp_path里造一个软链接再调用:

def test_resolve_follows_symlink(tmp_path): real = tmp_path / "real" real.mkdir() (real / "a.py").write_text("") link = tmp_path / "link" link.symlink_to(real) assert (link / "a.py").resolve().parent == real.resolve()

在 Windows 上创建软链接可能需要额外权限,这类测试记得加跳过标记,否则会在某些 CI 上挂掉——这是我在一个跨平台项目里付出过的代价,当时为了一个软链接测试折腾了一下午的 CI 配置。

注意:Path.cwd()和Path(__file__)都返回Path对象,但os.getcwd()和__file__返回的是str。混用时类型不一致容易出错,尤其在做相等比较的时候。项目里统一用一种风格,别一半 str 一半 Path。

最后分享一个我自己的小习惯:在程序的启动日志里固定打印两行,cwd=...和base=...,标上 INFO 级别。这行日志在正常运行时几乎没人看,但只要用户反馈"找不到文件",第一件事就是让他把这个日志贴出来,八成问题一眼就能定位。多花两行代码,省掉无数次来回沟通,这个投入产出比在踩过几次坑之后会格外明显。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/29 5:36:05

扎马尾有技巧:从发圈选择到蓬松固定,五年实战经验全解析

扎马尾这件事,我整整研究了五年,才敢说真正“会”了。ponytail看起来是所有发型里最简单的那个,一根发圈一套就完事,但为什么有些人随便一绑就是韩剧女主,有些人扎完就像刚起床的“炸毛星人”?差别全在细节…

作者头像 李华
网站建设 2026/10/29 5:35:28

深度学习张量切片算子StrideSlice原理与性能优化

1. 项目概述在深度学习框架和AI加速器的开发中,张量操作是最基础也是最核心的功能之一。StrideSlice作为CANN ops-nn中的一个重要算子,负责实现张量的切片操作,其性能直接影响模型推理和训练的效率。今天我们就来深入解析这个看似简单却暗藏玄…

作者头像 李华
网站建设 2026/10/29 5:34:20

用HyperFrame精准规划延时摄影:机位、光线与时间一次算清

去年秋天我拍了一次城市日出延时,提前一天踩好的机位,凌晨四点半出门,五点二十架好机器,当时只算了个“六点零三分日出”,结果太阳从画面最左侧两栋楼的夹缝里冒出来,一出来就是硬光,我想要的暖…

作者头像 李华
网站建设 2026/10/29 5:34:05

营销技能全景拆解:从用户洞察到数据优化的四层能力模型

营销这行有个很有意思的现象:很多人干了好几年,复盘时候能说清"我做了什么",但说不清"我为什么这么做",更说不清"换个场景我该怎么做"。我见过太多人把刷到过的一个爆款、一个黑话、一套模板当成营…

作者头像 李华
网站建设 2026/10/29 5:33:48

嵌入式警告系统设计:从035电压波动告警看工业级预警机制

1. 项目概述:这不是报错,是系统在悄悄给你递纸条“035-警告系统warnings”这个标题乍看像一串编号加术语的组合,但如果你在嵌入式开发、工业控制、自动化产线调试,或者哪怕只是拆过几台老式工控机,看到“035”这个前缀…

作者头像 李华
网站建设 2026/10/29 5:33:47

hyperframes参考架构实战:从GPU利用率到分布式训练优化

最近圈里好几个群都在聊 hyperframes,有人拿它跟传统 AI 训练方案比,有人拿它跟云上容器化方案比,还有人直接问“这东西到底是不是又一个套壳框架”。我因为工作原因,正好带团队做过一轮相关的性能验证和落地评估,从硬…

作者头像 李华