疑难问题(六)——VSCode 中 import ssqueezepy 卡死问题
前置条件
Python版本:3.9.19
1. 问题现象
在 Windows + Anaconda + VSCode 环境中,项目代码里执行:
import ssqueezepy
时,程序没有立刻报错,而是表现为卡死、无响应。最初看起来像是 ssqueezepy 包本身导入失败,或者 VSCode/Jupyter 在执行 Python 代码时卡住。
为了排查,写了一个最小测试脚本:
import os
import sys
import time
import faulthandler
os.environ["SSQ_PARALLEL"] = "0"
os.environ["NUMBA_NUM_THREADS"] = "1"
os.environ["OMP_NUM_THREADS"] = "1"
os.environ["MKL_NUM_THREADS"] = "1"
os.environ["OPENBLAS_NUM_THREADS"] = "1"
faulthandler.enable()
faulthandler.dump_traceback_later(15, repeat=True)
print("Python:", sys.executable, flush=True)
print("Version:", sys.version, flush=True)
def test_import(name):
print(f"\n>>> importing {name} ...", flush=True)
t0 = time.time()
__import__(name)
print(f"<<< imported {name} in {time.time() - t0:.2f}s", flush=True)
for mod in [
"numpy",
"scipy",
"llvmlite",
"numba",
"ssqueezepy",
]:
test_import(mod)
print("\nALL OK", flush=True)
结果显示:
-
numpy可以正常导入; -
scipy可以正常导入; -
llvmlite可以正常导入; -
numba可以正常导入; -
但卡在
ssqueezepy导入阶段。
因此问题不是普通的 Python 包不存在,也不是 numpy/scipy/numba 完全不能导入,而是 ssqueezepy 导入时触发了某个底层机制。
2. 关键错误线索
通过 faulthandler 输出的 traceback,可以看到程序反复卡在类似调用链上:
ssqueezepy\algos.py
→ numba.core.decorators
→ numba.core.dispatcher.enable_caching
→ numba.core.caching.ensure_cache_path
→ tempfile.NamedTemporaryFile
→ tempfile._mkstemp_inner
这说明:
-
ssqueezepy在 import 阶段加载了algos.py; -
algos.py中存在 Numba JIT 装饰的函数,并启用了缓存,即类似:
@jit(..., cache=True)
-
Numba 在导入阶段尝试启用磁盘缓存;
-
启用缓存时,Numba 需要确认 cache path 是否存在、是否可写;
-
它会在缓存路径中尝试创建临时文件;
-
程序正是卡在这个缓存路径检查/临时文件创建阶段。
因此核心问题不是:
ssqueezepy 算法计算太慢
而是:
ssqueezepy import 阶段触发 Numba cache=True,Numba 默认缓存路径在 VSCode 环境中卡住
3. 为什么一开始以为是 ssqueezepy 的问题
因为现象直接发生在:
import ssqueezepy
而且普通禁用并行的写法也不能解决:
import os
os.environ["SSQ_PARALLEL"] = "0"
os.environ["NUMBA_NUM_THREADS"] = "1"
os.environ["OMP_NUM_THREADS"] = "1"
os.environ["MKL_NUM_THREADS"] = "1"
os.environ["OPENBLAS_NUM_THREADS"] = "1"
import ssqueezepy
这说明问题不是简单的多线程占满 CPU,也不是 SSQ_PARALLEL 引起的。
真正的卡点在 Numba 的缓存机制,而 ssqueezepy 只是触发了这个机制。
4. 关键对照实验
4.1 Anaconda Prompt 中直接导入成功
在原来的 conda 环境 py030919 中,使用 Anaconda Prompt 执行:
conda activate py030919
python -c "import ssqueezepy; print('ok')"
结果瞬间输出:
ok
这说明:
原 conda 环境没有坏,ssqueezepy 包本身也不是完全不可用
4.2 在 Anaconda Prompt 中切到项目目录也正常
进入项目目录:
cd /d E:\XXX\XXX
python -c "import os, sys; print(os.getcwd()); print(sys.executable); import ssqueezepy; print('ok')"
也能正常输出 ok。
这说明项目路径本身不是直接原因。
4.3 在 Anaconda Prompt 中运行测试脚本也正常
执行:
python -u test_ssqueezepy.py
结果显示:
>>> importing numpy ...
<<< imported numpy in 0.06s
>>> importing scipy ...
<<< imported scipy in 0.01s
>>> importing llvmlite ...
<<< imported llvmlite in 0.00s
>>> importing numba ...
<<< imported numba in 0.13s
>>> importing ssqueezepy ...
<<< imported ssqueezepy in 0.54s
ALL OK
这进一步说明:
原环境、项目目录、测试脚本本身都不是根本问题
4.4 VSCode PowerShell 中卡死
在 VSCode 集成终端中运行同样的脚本时,仍然卡在:
numba.core.caching.ensure_cache_path
→ tempfile.NamedTemporaryFile
这说明问题和 VSCode 启动 Python 的环境有关,尤其是 Numba 默认 cache path 的选择和写入有关。
. 决定性修复实验
在 VSCode PowerShell 中手动设置:
$env:NUMBA_CACHE_DIR = "E:\numba_cache"
python -u .\test_ssqueezepy.py
脚本立刻正常运行。
这说明问题被定位为:
Numba 默认缓存目录在 VSCode PowerShell 环境下存在卡死问题
只要显式指定一个简单、可写、权限干净的缓存目录,问题就消失。
6. 根本原因总结
最终可以把问题概括为:
ssqueezepy 在 import 时会导入带有 Numba JIT 的函数。
这些函数启用了 cache=True。
Numba 因此会在 import 阶段初始化磁盘缓存。
在 VSCode PowerShell 环境中,Numba 默认选择的缓存路径或缓存检查逻辑发生卡死。
显式指定 NUMBA_CACHE_DIR 后,Numba 不再走默认路径,问题解决。
也就是说,这不是传统意义上的:
包没装好
代码写错
Python 版本不对
VSCode 解释器选错
而是更细的环境问题:
Numba cache=True + ssqueezepy import + Windows + VSCode PowerShell 默认缓存路径
7. 最终长期解决方案
7.1 建立隐藏的 Numba 缓存目录
为了保持 E 盘根目录整洁,不在 E 盘根目录直接创建散乱目录,而是在已有的 xxx 文件夹下建立隐藏缓存目录:
E:\xxx\.numba_cache
执行:
New-Item -ItemType Directory -Force "E:\xxx\.numba_cache" | Out-Null
attrib +h "E:\xxx\.numba_cache"
这样:
-
E 盘根目录仍然只有
xxx; -
Numba 缓存集中放在
E:\xxx\.numba_cache; -
资源管理器默认不会显示这个目录。
7.2 明确工作区层级
实际 VSCode 打开的不是单独的 Signal 文件夹,而是 xxx 工作区:
E:\xxx\Project\Python\xxx\xxx
并且 VSCode 使用的是:
xxx.code-workspace
因此 ${workspaceFolder} 指的是:
E:\xxx\Project\Python\xxx\xxx
而不是:
E:\xxx\Project\Python\xxx\xxx\Signal
所以 .env 应放在:
E:\xxx\Project\Python\xxx\xxx\.env
而不是:
E:\xxx\Project\Python\xxx\xxx\Signal\.env
7.3 xxx.env 内容
在:
E:\xxx\Project\Python\xxx\xxx\.env
写入:
NUMBA_CACHE_DIR=E:\xxx\.numba_cache
SSQ_PARALLEL=0
NUMBA_NUM_THREADS=1
OMP_NUM_THREADS=1
MKL_NUM_THREADS=1
OPENBLAS_NUM_THREADS=1
其中最关键的是:
NUMBA_CACHE_DIR=E:\xxx\.numba_cache
其余线程变量用于让 ssqueezepy、Numba、BLAS、OpenMP 等底层库在 VSCode 中先以稳定优先的方式运行。
7.4 修改 xxx.code-workspace
因为当前打开的是 .code-workspace 工作区,所以真正生效的工作区设置在:
xxx.code-workspace
其中内容应类似:
{
"folders": [
{
"path": "."
}
],
"settings": {
"workbench.colorTheme": "Monokai",
"python.defaultInterpreterPath": "C:\\ProgramData\\anaconda3\\envs\\py030919\\python.exe",
"python.envFile": "${workspaceFolder}/.env",
"terminal.integrated.env.windows": {
"NUMBA_CACHE_DIR": "E:\\xxx\\.numba_cache",
"SSQ_PARALLEL": "0",
"NUMBA_NUM_THREADS": "1",
"OMP_NUM_THREADS": "1",
"MKL_NUM_THREADS": "1",
"OPENBLAS_NUM_THREADS": "1"
},
"python.analysis.indexing": false,
"python.analysis.autoImportCompletions": false
}
}
这里的作用是:
- 固定 VSCode 使用正确解释器:
C:\ProgramData\anaconda3\envs\py030919\python.exe
- 让 Python Run / Debug 读取:
${workspaceFolder}/.env
- 让 VSCode 集成终端也继承:
NUMBA_CACHE_DIR=E:\xxx\.numba_cache
7.5 调试配置 launch.json
为了以后可以正常 F5 调试涉及 ssqueezepy 的代码,需要在工作区中建立调试配置。
如果使用 .vscode,路径为:
E:\xxx\Project\Python\xxx\xxx\.vscode\launch.json
内容:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug current Python file",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"cwd": "${workspaceFolder}",
"envFile": "${workspaceFolder}/.env",
"env": {
"NUMBA_CACHE_DIR": "E:\\xxx\\.numba_cache",
"SSQ_PARALLEL": "0",
"NUMBA_NUM_THREADS": "1",
"OMP_NUM_THREADS": "1",
"MKL_NUM_THREADS": "1",
"OPENBLAS_NUM_THREADS": "1"
},
"justMyCode": false
}
]
}
这样以后按 F5 调试当前 Python 文件时,也会显式继承 NUMBA_CACHE_DIR。
8. 最终验证方式
8.1 验证 VSCode 终端是否拿到环境变量
在 VSCode 终端中执行:
echo $env:NUMBA_CACHE_DIR
正确输出应为:
E:\xxx\.numba_cache
如果没有输出,说明当前 VSCode 终端没有继承工作区设置,需要:
-
保存
xxx.code-workspace; -
删除旧终端;
-
Developer: Reload Window; -
新开终端再测试。
8.2 验证 Python 解释器和缓存目录
执行:
python -c "import os, sys; print(sys.executable); print(os.environ.get('NUMBA_CACHE_DIR')); import ssqueezepy; print('ok')"
正确输出应类似:
C:\ProgramData\anaconda3\envs\py030919\python.exe
E:\xxx\.numba_cache
ok
8.3 验证测试脚本
在 xxx 一级执行:
python -u .\Signal\test_ssqueezepy.py
或者进入 Signal 后执行:
cd "E:\xxx\Project\Python\xxx\xxx\Signal"
python -u .\test_ssqueezepy.py
正确结果应包含:
>>> importing ssqueezepy ...
<<< imported ssqueezepy in ...s
ALL OK
9. 最终项目结构
当前推荐结构:
E:\xxx
├── .numba_cache ← 隐藏,Numba 固定缓存目录
└── Project
└── Python
└── xxx
└── xxx
├── .env ← 工作区环境变量
├── xxx.code-workspace
├── .vscode
│ └── launch.json
├── Signal
│ └── test_ssqueezepy.py
└── 其他项目文件
如果 settings 已经写入 xxx.code-workspace,则 .vscode/settings.json 不是必须的。当前关键配置入口是:
xxx.code-workspace
其它
- 本文隐去了路径中的个性化信息阅读起来可能会有麻烦,但是总结一下就是
.env文件跟 .code-workspace在一级,.vscode下面的文件就正常了,因为按照作者的经验.vscode应该是在创建工作区的时候就自动生成了,应该不会出现路径层级的问题 ; - 在工作区的文件都调整完毕之后,**一定注意修改 xxx.code-workspace!**具体操作是:在 VSCode 里按
Ctrl + Shift + P;输入并选择Preferences: Open Workspace Settings (JSON);按照7.4的方法改;最后重载 VSCode,并新建终端(注意是新建,不是清空)。 - 在执行上述操作之后,还可以跑一段官方例程确保 ssqueezepy 确实已经正常工作,例程如下:
import numpy as np
import matplotlib.pyplot as plt
from ssqueezepy import ssq_cwt, ssq_stft
from ssqueezepy.experimental import scale_to_freq
def viz(x, Tx, Wx):
plt.imshow(np.abs(Wx), aspect='auto', cmap='turbo')
plt.show()
plt.imshow(np.abs(Tx), aspect='auto', vmin=0, vmax=.2, cmap='turbo')
plt.show()
#%%# Define signal ####################################
N = 2048
t = np.linspace(0, 10, N, endpoint=False)
xo = np.cos(2 * np.pi * 2 * (np.exp(t / 2.2) - 1))
xo += xo[::-1] # add self reflected
x = xo + np.sqrt(2) * np.random.randn(N) # add noise
plt.plot(xo); plt.show()
plt.plot(x); plt.show()
#%%# CWT + SSQ CWT ####################################
Twxo, Wxo, *_ = ssq_cwt(xo)
viz(xo, Twxo, Wxo)
Twx, Wx, *_ = ssq_cwt(x)
viz(x, Twx, Wx)
#%%# STFT + SSQ STFT ##################################
Tsxo, Sxo, *_ = ssq_stft(xo)
viz(xo, np.flipud(Tsxo), np.flipud(Sxo))
Tsx, Sx, *_ = ssq_stft(x)
viz(x, np.flipud(Tsx), np.flipud(Sx))
#%%# With units #######################################
from ssqueezepy import Wavelet, cwt, stft, imshow
fs = 400
t = np.linspace(0, N/fs, N)
wavelet = Wavelet()
Wx, scales = cwt(x, wavelet)
Sx = stft(x)[::-1]
freqs_cwt = scale_to_freq(scales, wavelet, len(x), fs=fs)
freqs_stft = np.linspace(1, 0, len(Sx)) * fs/2
ikw = dict(abs=1, xticks=t, xlabel="Time [sec]", ylabel="Frequency [Hz]")
imshow(Wx, **ikw, yticks=freqs_cwt)
imshow(Sx, **ikw, yticks=freqs_stft)
更多推荐
所有评论(0)