在使用 PyInstallerPython 脚本打包为独立可执行文件时, Path.cwd()sys.executablesys.argvPath(__file__) 的行为会发生变化。理解这些差异有助于避免路径相关问题。以下是具体分析:


Path.cwd()

打包前(直接运行 Python 脚本)

当直接通过 Python 解释器运行脚本(如 python script.py)时,Path.cwd()返回的是​​执行脚本时终端(或命令行)的当前工作目录​​。

这个路径由用户在终端中执行脚本时的位置决定,与脚本本身的存储位置无关。

假设脚本 script.py存储在 D:\project\src,但用户在终端中执行以下操作:

# 当前终端工作目录是 D:\project
cd D:\project
python src\script.py  # 执行脚本

此时 Path.cwd()返回 D:\project(终端的当前目录),而非脚本所在的 src目录。

打包后(运行 PyInstaller 生成的可执行文件)

PyInstaller 会将脚本及其依赖打包为一个独立的可执行文件(如 script.exe)。此时 Path.cwd() 的行为取决于用户启动可执行文件的方式

(1) 通过命令行显式启动

如果在终端中通过命令行启动可执行文件(如 .\dist\script.exe),则 Path.cwd() 返回终端的当前工作目录(与打包前的逻辑一致)。

示例
可执行文件 script.exe 存储在 D:\dist,用户在终端中执行:

# 当前终端工作目录是 D:\test
cd D:\test
D:\dist\script.exe  # 启动可执行文件

此时 Path.cwd() 返回 D:\test(终端的当前目录),而非 D:\distexe 文件所在目录)。

(2) 通过文件管理器双击启动

如果通过文件管理器(如 Windows 资源管理器)直接双击可执行文件启动,Path.cwd() 通常返回可执行文件所在的目录
(这是多数操作系统的默认行为)。

示例
双击 D:\dist\script.exe,此时 Path.cwd() 返回 D:\distexe 文件所在的目录)。

关键区别总结

场景 打包前(Python 脚本) 打包后(可执行文件)
路径决定因素 终端执行脚本时的当前目录 取决于启动方式:
- 命令行启动:终端当前目录
- 文件管理器双击:通常为 exe 所在目录
与脚本/exe 位置的关系 无关(仅由终端位置决定) 无关(除非是双击方式启动)

sys.executable

打包前

在普通 Python 脚本中,sys.executable 返回 当前 Python 解释器的可执行文件(exe) 路径,例如:

import sys
print(sys.executable)
# 输出示例: "D:\\Python3.10\\python.exe"

打包后

PyInstaller 打包后的可执行文件运行时,sys.executable 会指向 当前运行的打包文件(exe) 本身,而非 Python 解释器。例如:

import sys
print(sys.executable)
# 输出示例: "C:\\dist\\my_app.exe"

此变化是因为 PyInstallerPython 解释器和依赖库“冻结”到可执行文件中。


sys.argv

打包前

sys.argv 是一个列表,包含命令行参数。第一个元素 sys.argv[0] 通常是当前脚本文件名,其余元素是脚本接收到的命令行参数。
注意:sys.argv[0]就是在Path.cwd()下指定的要执行的Python脚本文件,所以可以是绝对路径,也可以是Path.cwd()的相对路径

import sys
print(f'sys.argv[0] = {sys.argv[0]}')

运行命令

E:\workspace-pycharm\one-api>python E:\workspace-pycharm\install_demo\src\main.py arg1 arg2
sys.argv[0] = E:\workspace-pycharm\install_demo\src\main.py

E:\workspace-pycharm\one-api>cd ..\install_demo
E:\workspace-pycharm\install_demo>python src\main.py arg1 arg2
sys.argv[0] = src\main.py

E:\workspace-pycharm\install_demo>cd src
E:\workspace-pycharm\install_demo\src>python main.py arg1 arg2
sys.argv[0] = main.py

打包后

打包后的可执行文件运行时,sys.argv[0] 会变为可执行文件的路径,后续参数保持不变:

import sys
print(sys.argv)
# 运行命令: my_app.exe arg1 arg2
# 输出: ['my_app.exe', 'arg1', 'arg2']

Path(__file__)

打包前

__file__ 表示当前脚本的文件路径Path(__file__).resolve() 可获取绝对路径:

from pathlib import Path
print(Path(__file__).resolve())
# 输出示例: "D:\\project\\my_script.py"

打包后

PyInstaller 会将脚本打包到临时目录中,__file__ 的路径会指向解压后的临时文件夹。例如:

from pathlib import Path
print(Path(__file__).resolve())
# 输出示例: "C:\\Users\\User\\AppData\\Local\\Temp\\_MEI1234\\my_script.py"

若需获取打包后的可执行文件路径,可结合 sys.executable

from pathlib import Path
import sys
print(Path(sys.executable).resolve())
# 输出示例: "C:\\dist\\my_app.exe"

应用场景与解决方案

  1. 一起被打包的资源文件路径问题
    若脚本依赖一些一起被打包的资源文件(如js文件、图片),打包后直接使用 Path(__file__) 会失败。推荐利用 PyInstaller 特有的 sys._MEIPASS 变量(类型为str)来解决(根据项目结构视情况稍作调整):
import sys
from pathlib import Path

if getattr(sys, 'frozen', False):
    # 运行在 PyInstaller 打包后的环境中
    # sys._MEIPASS 是在使用 PyInstaller 打包 Python 程序时生成的一个特殊属性。它指向一个临时目录,该目录包含了程序运行所需的所有依赖文件
    base_dir = Path(sys._MEIPASS)
else:
    # 运行在本地环境中。
    base_dir = Path(__file__).parent
resource_path = base_dir / 'resource'
  1. 非打包的配置文件路径问题
    打包后若需访问与可执行文件同目录的非打包的文件(如配置文件),不要依赖 Path.cwd(),因为它可能因启动方式不同而变化。正确做法是通过 sys.executable 获取可执行文件的绝对路径,再提取其所在目录(根据项目结构视情况稍作调整):
import sys
from pathlib import Path

# 获取可执行文件所在的绝对目录(打包后有效)
if getattr(sys, 'frozen', False):
    # 打包后的场景(PyInstaller 会设置 `frozen` 属性)
    base_dir = Path(sys.executable).parent.resolve()
else:
    # 未打包的场景(直接运行脚本)
    base_dir = Path(__file__).parent.resolve()

# 使用 base_dir 构造资源路径(如读取同目录的 config.yaml)
config_path = base_dir / "config.yaml"
  1. 参数传递与调试
    打包后的程序仍可通过命令行传递参数,但需注意 sys.argv[0] 的变化。调试时建议输出完整参数列表以确认行为。

总结

变量/场景 打包前 打包后
Path.cwd() 终端执行脚本时的当前目录 取决于启动方式:
- 命令行启动:终端当前目录
- 文件管理器双击:通常为 exe 所在目录
sys.executable Python 解释器路径 可执行文件自身路径
sys.argv[0] 脚本文件名 可执行文件名
Path(__file__) 脚本文件绝对路径 临时解压目录中的路径

理解这些差异后,可以更安全地处理路径和资源加载问题,确保程序在打包前后均能正常运行。

Logo

电影级数字人,免显卡端渲染SDK,十行代码即可调用,工业级demo免费开源下载!

更多推荐