前置条件

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

这说明:

  1. ssqueezepy 在 import 阶段加载了 algos.py

  2. algos.py 中存在 Numba JIT 装饰的函数,并启用了缓存,即类似:

@jit(..., cache=True)
  1. Numba 在导入阶段尝试启用磁盘缓存;

  2. 启用缓存时,Numba 需要确认 cache path 是否存在、是否可写;

  3. 它会在缓存路径中尝试创建临时文件;

  4. 程序正是卡在这个缓存路径检查/临时文件创建阶段。

因此核心问题不是:

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
    }
}

这里的作用是:

  1. 固定 VSCode 使用正确解释器:
C:\ProgramData\anaconda3\envs\py030919\python.exe
  1. 让 Python Run / Debug 读取:
${workspaceFolder}/.env
  1. 让 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 终端没有继承工作区设置,需要:

  1. 保存 xxx.code-workspace

  2. 删除旧终端;

  3. Developer: Reload Window

  4. 新开终端再测试。


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

其它

  1. 本文隐去了路径中的个性化信息阅读起来可能会有麻烦,但是总结一下就是 .env 文件跟 . code-workspace 在一级,.vscode 下面的文件就正常了,因为按照作者的经验 .vscode 应该是在创建工作区的时候就自动生成了,应该不会出现路径层级的问题 ;
  2. 在工作区的文件都调整完毕之后,**一定注意修改 xxx.code-workspace!**具体操作是:在 VSCode 里按 Ctrl + Shift + P ;输入并选择 Preferences: Open Workspace Settings (JSON)按照 7.4 的方法改最后重载 VSCode,并新建终端(注意是新建,不是清空)
  3. 在执行上述操作之后,还可以跑一段官方例程确保 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)

更多推荐