VS Code 中 Python 相对路径失效?从 FileNotFoundError 深入理解当前工作目录问题

1. 问题背景

在 Python 数据分析项目中,我们经常会把代码文件和数据文件分开放置,例如:

sample_turbine_mast_analysis
├─ output
│  └─ all_files_resampled_20260611_162930.csv
│
└─ veer_power_analysis
   └─ 01_preprocessing
      └─ m1_nacelle_wind_bias.py

当前运行的 Python 文件是:

sample_turbine_mast_analysis/veer_power_analysis/01_preprocessing/m1_nacelle_wind_bias.py

想读取的 CSV 文件是:

sample_turbine_mast_analysis/output/all_files_resampled_20260611_162930.csv

于是很自然地写出下面代码:

import pandas as pd

df = pd.read_csv("../../output/all_files_resampled_20260611_162930.csv")

从目录结构上看,这个路径似乎没问题:

当前 py 文件目录:
sample_turbine_mast_analysis/veer_power_analysis/01_preprocessing

../../output/
等价于:
sample_turbine_mast_analysis/output/

但运行时却报错:

FileNotFoundError: [Errno 2] No such file or directory:
'../../output/all_files_resampled_20260611_162930.csv'

这类问题在 PyCharm 中可能很少遇到,但在 VS Code 中非常常见。


2. 问题的本质:相对路径不是相对于 py 文件,而是相对于“当前工作目录”

很多初学者都会有一个误解:

Python 中的相对路径,是相对于当前正在运行的 .py 文件所在目录。

但真实情况通常是:

Python 中的相对路径,是相对于当前工作目录,也就是 os.getcwd()Path.cwd() 所指向的位置。

也就是说,代码中写的:

"../../output/all_files_resampled_20260611_162930.csv"

并不一定是从 m1_nacelle_wind_bias.py 所在目录开始往上找,而是从当前工作目录开始往上找。

可以用下面代码验证:

from pathlib import Path
import sys

print("当前运行的 Python 路径:")
print(sys.executable)

print("当前工作目录:")
print(Path.cwd())

print("当前 py 文件所在目录:")
print(Path(__file__).resolve().parent)

实际输出为:

当前运行的 Python 路径:
C:\Users\54867\.conda\envs\ewm_ti\python.exe

当前工作目录:
F:\09-code\20-LTC

这就说明,VS Code 当前运行时的起点并不是:

F:\09-code\20-LTC\sample_turbine_mast_analysis\veer_power_analysis\01_preprocessing

而是:

F:\09-code\20-LTC

因此这行代码:

pd.read_csv("../../output/all_files_resampled_20260611_162930.csv")

实际会被解释成:

F:\09-code\20-LTC\..\..\output\all_files_resampled_20260611_162930.csv

也就是:

F:\output\all_files_resampled_20260611_162930.csv

但真实文件在:

F:\09-code\20-LTC\sample_turbine_mast_analysis\output\all_files_resampled_20260611_162930.csv

所以必然报错。


3. 为什么 PyCharm 里经常没问题,而 VS Code 容易出问题?

因为二者默认的运行习惯不同。

PyCharm 通常会比较明确地设置运行配置,比如:

  • 当前脚本路径;
  • 当前工作目录;
  • Python 解释器;
  • 环境变量;
  • 参数配置。

很多情况下,PyCharm 的运行目录会更接近当前脚本所在目录,或者至少能在 Run Configuration 里直观看到。

而 VS Code 更灵活,但也更容易“隐式”出问题。它可能把下面这个目录作为当前工作目录:

F:\09-code\20-LTC

也就是 VS Code 当前打开的工作区根目录。

这就会导致一个现象:

同一段相对路径代码,在 PyCharm 能跑,在 VS Code 报 FileNotFoundError

本质不是 pandas 的问题,也不是文件不存在,而是路径解析起点变了。


4. 第一种解决方案:让 VS Code 普通运行时从当前文件目录启动

如果只是点击 VS Code 右上角的普通运行按钮,例如:

Run Python File

可以设置:

{
    "python.terminal.executeInFileDir": true
}

设置方法:

  1. 打开 VS Code;
  2. Ctrl + Shift + P
  3. 输入 Preferences: Open User Settings (JSON)
  4. 加入:
{
    "python.terminal.executeInFileDir": true
}

这个配置的作用是:

普通运行 Python 文件时,把当前工作目录切换为当前 .py 文件所在目录。

设置之后,运行:

from pathlib import Path

print(Path.cwd())

期望输出应该变成:

F:\09-code\20-LTC\sample_turbine_mast_analysis\veer_power_analysis\01_preprocessing

这样:

pd.read_csv("../../output/all_files_resampled_20260611_162930.csv")

就可以正常找到文件。

但是要注意:这个配置主要影响普通运行,不一定影响 Debug。


5. 第二种解决方案:Debug 时配置 launch.json

如果使用的是 Debug,例如:

F5

或者左侧“运行和调试”,那么 VS Code 通常会读取项目下的:

.vscode/launch.json

这时需要显式设置 cwd

推荐配置如下:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python Debug 当前文件目录",
            "type": "debugpy",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal",
            "cwd": "${fileDirname}",
            "justMyCode": true
        }
    ]
}

其中最关键的是:

"cwd": "${fileDirname}"

意思是:

Debug 时,把当前工作目录设置为当前正在运行的 .py 文件所在目录。

如果 VS Code 当前打开的工作区是:

F:\09-code\20-LTC

那么 launch.json 应该放在:

F:\09-code\20-LTC\.vscode\launch.json

而不是放在:

F:\09-code\20-LTC\sample_turbine_mast_analysis\.vscode\launch.json

也不是放在更深层目录。

这是一个非常容易踩坑的点。


6. 有了 launch.json 仍然不生效,通常是这几个原因

6.1 launch.json 放错位置

假设 VS Code 打开的根目录是:

F:\09-code\20-LTC

那么 VS Code 会优先读取:

F:\09-code\20-LTC\.vscode\launch.json

如果你把配置文件放到了:

F:\09-code\20-LTC\sample_turbine_mast_analysis\.vscode\launch.json

但 VS Code 打开的根目录不是 sample_turbine_mast_analysis,那这个配置可能不会被使用。


6.2 Debug 时没有选中对应配置

创建了 launch.json 后,左侧“运行和调试”顶部会出现一个配置下拉框。

需要选择:

Python Debug 当前文件目录

然后再按 F5。

如果仍然使用默认的:

Python Debugger: Current File

那可能不会走你自定义的 cwd 配置。


6.3 点击的是右上角 Debug 按钮

VS Code 右上角的 Debug 按钮有时不走左侧选中的 Debug 配置。

如果想让右上角的 Debug 按钮也使用这个配置,可以尝试增加:

"purpose": ["debug-in-terminal"]

完整示例:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python Debug 当前文件目录",
            "type": "debugpy",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal",
            "cwd": "${fileDirname}",
            "purpose": ["debug-in-terminal"],
            "justMyCode": true
        }
    ]
}

不过更稳妥的做法是:

从左侧“运行和调试”中选择自己的配置,然后启动 Debug。


7. 最推荐的工程写法:不要依赖 VS Code 的当前工作目录

虽然可以通过 VS Code 设置解决问题,但从工程稳定性来说,更推荐直接在代码中基于当前 .py 文件定位路径。

也就是不要写:

pd.read_csv("../../output/all_files_resampled_20260611_162930.csv")

而是写:

from pathlib import Path
import pandas as pd

# 当前 py 文件:
# sample_turbine_mast_analysis/veer_power_analysis/01_preprocessing/m1_nacelle_wind_bias.py
current_file = Path(__file__).resolve()

# 项目根目录:
# sample_turbine_mast_analysis
project_root = current_file.parents[2]

# 目标 CSV 文件
csv_path = project_root / "output" / "all_files_resampled_20260611_162930.csv"

print("当前 py 文件:", current_file)
print("项目根目录:", project_root)
print("CSV 路径:", csv_path)
print("文件是否存在:", csv_path.exists())

df = pd.read_csv(csv_path, encoding="utf-8-sig")

这段代码不关心 VS Code 当前工作目录是什么。

无论你在:

  • VS Code 普通运行;
  • VS Code Debug;
  • PyCharm;
  • 命令行;
  • 定时任务;
  • 其他 IDE;

只要这个 .py 文件和 output 文件夹的相对位置不变,它就能稳定找到数据文件。


8. 为什么 Path(__file__).resolve().parents[2] 是项目根目录?

当前文件路径是:

F:\09-code\20-LTC\sample_turbine_mast_analysis\veer_power_analysis\01_preprocessing\m1_nacelle_wind_bias.py

那么:

Path(__file__).resolve().parent

得到:

F:\09-code\20-LTC\sample_turbine_mast_analysis\veer_power_analysis\01_preprocessing

继续往上:

Path(__file__).resolve().parents[0]

得到:

F:\09-code\20-LTC\sample_turbine_mast_analysis\veer_power_analysis\01_preprocessing
Path(__file__).resolve().parents[1]

得到:

F:\09-code\20-LTC\sample_turbine_mast_analysis\veer_power_analysis
Path(__file__).resolve().parents[2]

得到:

F:\09-code\20-LTC\sample_turbine_mast_analysis

output 文件夹正好在:

F:\09-code\20-LTC\sample_turbine_mast_analysis\output

所以可以拼接:

csv_path = project_root / "output" / "all_files_resampled_20260611_162930.csv"

最终得到:

F:\09-code\20-LTC\sample_turbine_mast_analysis\output\all_files_resampled_20260611_162930.csv

9. 建议封装一个通用路径工具

如果项目中很多脚本都需要读取 outputinputdata 等目录,可以封装一个简单函数。

例如在项目中创建:

sample_turbine_mast_analysis
└─ utils
   └─ paths.py

内容如下:

from pathlib import Path

def get_project_root() -> Path:
    """
    获取项目根目录:sample_turbine_mast_analysis
    当前文件位于 sample_turbine_mast_analysis/utils/paths.py
    所以 parent.parent 即为项目根目录。
    """
    return Path(__file__).resolve().parent.parent


def get_output_dir() -> Path:
    """
    获取 output 目录。
    """
    return get_project_root() / "output"


def get_output_file(filename: str) -> Path:
    """
    获取 output 目录下的某个文件路径。
    """
    return get_output_dir() / filename

然后在业务脚本中这样使用:

import pandas as pd
from utils.paths import get_output_file

csv_path = get_output_file("all_files_resampled_20260611_162930.csv")

print("CSV路径:", csv_path)
print("文件是否存在:", csv_path.exists())

df = pd.read_csv(csv_path, encoding="utf-8-sig")

这种写法的好处是:

  1. 路径逻辑集中管理;
  2. 每个脚本不用重复写 parents[2]
  3. 后续项目目录变化时,只需要改一个地方;
  4. 更适合多人协作和长期维护。

10. 另一个实用技巧:自动读取最新生成的文件

有些输出文件带有时间戳,例如:

all_files_resampled_20260611_162930.csv

这种文件名容易变,如果代码写死文件名,下一次重新生成文件后就可能找不到。

可以改成自动读取最新文件:

from pathlib import Path
import pandas as pd

project_root = Path(__file__).resolve().parents[2]
output_dir = project_root / "output"

csv_files = list(output_dir.glob("all_files_resampled_*.csv"))

if not csv_files:
    raise FileNotFoundError(f"没有找到 all_files_resampled_*.csv 文件,目录:{output_dir}")

latest_csv = max(csv_files, key=lambda p: p.stat().st_mtime)

print("读取最新文件:", latest_csv)

df = pd.read_csv(latest_csv, encoding="utf-8-sig")

这样不需要每次手动改时间戳文件名。


11. 排查路径问题的固定模板

以后遇到路径问题,可以先加下面这段代码:

from pathlib import Path
import sys

print("=" * 80)
print("Python解释器:")
print(sys.executable)

print("\n当前工作目录 Path.cwd():")
print(Path.cwd())

print("\n当前 py 文件路径:")
print(Path(__file__).resolve())

print("\n当前 py 文件所在目录:")
print(Path(__file__).resolve().parent)

test_path = Path("../../output/all_files_resampled_20260611_162930.csv")

print("\n原始相对路径:")
print(test_path)

print("\n解析后的绝对路径:")
print(test_path.resolve())

print("\n文件是否存在:")
print(test_path.exists())
print("=" * 80)

这段代码可以快速回答三个关键问题:

  1. 当前 Python 解释器是不是对的;
  2. 当前工作目录到底在哪里;
  3. 相对路径最终被解析到了哪里。

只要把这三个问题搞清楚,绝大多数 FileNotFoundError 都能快速定位。


12. 最终推荐方案

对于 VS Code,可以做两层处理。

第一层,改善 VS Code 运行体验:

用户级 settings.json 中加入:

{
    "python.terminal.executeInFileDir": true
}

项目级 .vscode/launch.json 中加入:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python Debug 当前文件目录",
            "type": "debugpy",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal",
            "cwd": "${fileDirname}",
            "justMyCode": true
        }
    ]
}

第二层,代码中不要依赖 IDE 的当前工作目录,而是使用:

from pathlib import Path

project_root = Path(__file__).resolve().parents[2]
csv_path = project_root / "output" / "all_files_resampled_20260611_162930.csv"

这才是最稳的方式。


13. 总结

这次问题的核心不是文件不存在,也不是 pandas 读取 CSV 的问题,而是:

VS Code 的当前工作目录和当前 Python 文件所在目录不是一回事。

相对路径:

"../../output/xxx.csv"

看起来是从当前 .py 文件出发,但实际是从 Path.cwd() 出发。

因此,只要 VS Code 当前工作目录不是你以为的位置,路径就会找偏,最终报:

FileNotFoundError

解决这类问题,最重要的是记住一句话:

写给自己临时调试的脚本,可以调整 VS Code 的 cwd;写给项目长期使用的代码,应该基于 __file__ 构造绝对路径。

这样才能避免同一段代码在 PyCharm 能跑、VS Code 不能跑、命令行又报错的问题。

更多推荐