PyInstaller 调试详细指南
PyInstaller 调试详细指南
PyInstaller 提供了多种调试工具和选项来帮助解决打包过程中的问题。以下是完整的调试方法和技巧:
1. 基本调试选项
启用详细输出
# 显示详细的构建信息
pyinstaller --log-level DEBUG main.py
# 或者使用简写
pyinstaller -d all main.py
日志级别选项
# 不同详细程度的日志
pyinstaller --log-level TRACE main.py # 最详细
pyinstaller --log-level DEBUG main.py # 调试信息
pyinstaller --log-level INFO main.py # 一般信息(默认)
pyinstaller --log-level WARN main.py # 只显示警告
pyinstaller --log-level ERROR main.py # 只显示错误
2. 调试模式打包
启用调试模式
# 启用所有调试功能
pyinstaller --debug all main.py
# 启用特定调试功能
pyinstaller --debug imports main.py # 调试导入
pyinstaller --debug bootloader main.py # 调试引导程序
调试模式的具体效果
- 不压缩:生成的 EXE 文件不压缩,便于分析
- 保留临时文件:不清理构建过程中的临时文件
- 详细日志:输出更详细的处理信息
- 包含调试符号:保留调试信息
3. 分析导入依赖
生成导入分析报告
# 生成导入分析 JSON 文件
pyinstaller --json analysis.json main.py
# 分析特定模块的导入
pyinstaller --log-level DEBUG --debug imports main.py
手动分析依赖树
# 使用 pip 检查依赖
pip show package_name
# 使用 Python 检查模块路径
python -c "import module; print(module.__file__)"
4. 运行时调试
保留控制台窗口
# 即使 GUI 程序也显示控制台
pyinstaller --console main.py
# 或者在代码中强制显示控制台
import sys
sys.stdout = open('stdout.log', 'w')
sys.stderr = open('stderr.log', 'w')
添加运行时调试代码
import sys
import os
import traceback
def setup_debugging():
"""设置调试环境"""
# 创建调试日志文件
debug_log = open('debug.log', 'w', encoding='utf-8')
# 重定向标准输出和错误
sys.stdout = debug_log
sys.stderr = debug_log
# 设置异常钩子
def exception_handler(exc_type, exc_value, exc_traceback):
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
debug_log.write("未捕获的异常:\n")
traceback.print_exception(exc_type, exc_value, exc_traceback, file=debug_log)
debug_log.flush()
sys.excepthook = exception_handler
# 记录启动信息
print(f"程序启动: {sys.argv}")
print(f"Python 路径: {sys.path}")
print(f"工作目录: {os.getcwd()}")
return debug_log
# 在程序开始时调用
if __name__ == "__main__":
debug_log = setup_debugging()
try:
# 你的主程序代码
main()
except Exception as e:
print(f"程序异常: {e}")
traceback.print_exc()
finally:
debug_log.close()
5. 检查打包内容
分析生成的可执行文件
# 检查 EXE 文件信息(Windows)
strings.exe dist/main.exe | findstr "python"
# 或者使用 Linux 的 strings 命令
strings dist/main | grep python
# 使用 UPX 解压(如果使用了压缩)
upx -d dist/main.exe
检查临时文件
# 在调试模式下保留临时文件
pyinstaller --debug all --noclean main.py
# 然后检查 build 目录
ls -la build/main/
6. 特定问题调试
调试隐藏导入问题
# 显示所有导入分析
pyinstaller --log-level DEBUG --debug imports main.py 2> import_debug.log
# 检查生成的导入树
cat build/main/ Analysis-00.toc
调试二进制依赖
# 检查二进制依赖
pyinstaller --log-level DEBUG main.py 2> binary_debug.log
# 使用 ldd 检查 Linux 依赖(Windows 用 depends.exe)
ldd dist/main
调试数据文件
# 详细数据文件处理日志
pyinstaller --log-level DEBUG --debug all main.py 2> data_debug.log
7. 创建调试专用版本
调试版 spec 文件
# debug.spec
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=True, # 不打包成 archive,便于调试
)
# 添加调试钩子
def debug_runtime_hook():
import sys
import os
debug_file = open(os.path.join(sys._MEIPASS, 'runtime_debug.log'), 'w')
sys.stdout = debug_file
sys.stderr = debug_file
runtime_hooks = [debug_runtime_hook]
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
name='main_debug',
debug=True, # 包含调试信息
bootloader_ignore_signals=False,
strip=False, # 不剥离符号
upx=False, # 不使用压缩
runtime_tmpdir=None,
console=True, # 总是显示控制台
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
8. 高级调试技巧
使用 PyInstaller 的测试模式
# 测试模式,不实际打包
pyinstaller --dry-run main.py
# 只分析依赖
pyinstaller --log-level DEBUG --dry-run main.py > analysis.txt
检查钩子执行
# 创建调试钩子 hooks/hook-debug.py
def pre_safe_import_module(api):
print(f"处理模块: {api.module_name}")
def pre_find_module_path(api):
print(f"查找模块路径: {api.module_name}")
内存和性能调试
# 使用内存分析工具
pip install memory_profiler
# 在代码中添加内存分析
@profile
def memory_intensive_function():
# 你的代码
pass
9. 常见问题诊断
诊断 "Failed to execute script" 错误
# 在代码开头添加详细错误处理
import sys
import traceback
def main():
try:
# 你的主程序逻辑
pass
except Exception as e:
# 将错误信息写入文件
with open('error.log', 'w') as f:
f.write(f"错误类型: {type(e).__name__}\n")
f.write(f"错误信息: {str(e)}\n")
f.write("追踪信息:\n")
traceback.print_exc(file=f)
# 重新抛出异常
raise
if __name__ == '__main__':
main()
诊断导入错误
# 检查所有导入
import pkg_resources
import importlib
def check_imports():
required_modules = [
'numpy', 'pandas', 'PyQt5', 'os', 'sys'
]
for module in required_modules:
try:
importlib.import_module(module)
print(f"✓ {module} 导入成功")
except ImportError as e:
print(f"✗ {module} 导入失败: {e}")
check_imports()
10. 调试工具和脚本
自动化调试脚本
# debug_build.py
import subprocess
import sys
import os
from datetime import datetime
def debug_build():
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
log_file = f"build_debug_{timestamp}.log"
print(f"开始调试构建,日志文件: {log_file}")
# 构建命令
cmd = [
sys.executable, "-m", "PyInstaller",
"--log-level=DEBUG",
"--debug=all",
"--console",
"--noclean",
"main.py"
]
# 执行构建并记录日志
with open(log_file, 'w', encoding='utf-8') as log:
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
universal_newlines=True
)
# 实时输出并记录
for line in process.stdout:
print(line, end='')
log.write(line)
log.flush()
process.wait()
print(f"构建完成,返回码: {process.returncode}")
# 分析构建结果
if process.returncode == 0:
print("✓ 构建成功")
analyze_build()
else:
print("✗ 构建失败")
analyze_errors(log_file)
def analyze_build():
"""分析构建结果"""
print("\n=== 构建分析 ===")
# 检查生成的文件
if os.path.exists("dist/main.exe"):
print("✓ 可执行文件已生成")
file_size = os.path.getsize("dist/main.exe") / (1024 * 1024)
print(f" 文件大小: {file_size:.2f} MB")
else:
print("✗ 可执行文件未生成")
# 检查临时文件
if os.path.exists("build/main"):
print("✓ 临时文件目录存在")
# 可以进一步分析临时文件内容
def analyze_errors(log_file):
"""分析错误日志"""
print(f"\n=== 错误分析 ===")
with open(log_file, 'r', encoding='utf-8') as f:
content = f.read()
# 查找常见错误模式
error_patterns = [
("ModuleNotFoundError", "模块未找到"),
("ImportError", "导入错误"),
("FileNotFoundError", "文件未找到"),
("PermissionError", "权限错误"),
]
for pattern, description in error_patterns:
if pattern in content:
print(f"⚠ 发现 {description}")
if __name__ == "__main__":
debug_build()
运行时诊断工具
# runtime_diagnostics.py
import sys
import os
import platform
def run_diagnostics():
"""运行时环境诊断"""
print("=== 运行时环境诊断 ===")
# 系统信息
print(f"系统平台: {platform.system()} {platform.release()}")
print(f"Python 版本: {platform.python_version()}")
print(f"可执行文件: {sys.executable}")
print(f"工作目录: {os.getcwd()}")
print(f"参数: {sys.argv}")
# 环境变量
print(f"PATH: {os.environ.get('PATH', '')}")
# 检查 PyInstaller 特定环境
if hasattr(sys, '_MEIPASS'):
print(f"PyInstaller 临时目录: {sys._MEIPASS}")
else:
print("未在 PyInstaller 环境中运行")
# 检查模块导入
print("\n=== 模块导入检查 ===")
test_modules = ['os', 'sys', 'json', 'PyQt5', 'numpy']
for module in test_modules:
try:
__import__(module)
print(f"✓ {module}")
except ImportError:
print(f"✗ {module}")
# 检查文件访问
print("\n=== 文件访问检查 ===")
test_files = ['config.ini', 'data.json']
for file in test_files:
if os.path.exists(file):
print(f"✓ {file}")
else:
print(f"✗ {file}")
if __name__ == "__main__":
run_diagnostics()
11. 调试最佳实践
- 逐步调试:先解决一个错误,再处理下一个
- 对比测试:在打包环境和开发环境分别测试
- 最小复现:创建最小化的测试用例来复现问题
- 版本控制:记录每次调试的更改和结果
- 文档记录:记录解决方案供以后参考
通过以上调试方法,你可以系统地诊断和解决 PyInstaller 打包过程中的各种问题。
浙公网安备 33010602011771号