NVM 环境配置与 npm 找不到的排查

macOS / Linux 上配 nvm 先确认 nvm 安装位置: echo $NVM_DIR通常是 ~/.nvm。在 ~/.zshrc(zsh)或 ~/.bashrc(bash)末尾加: export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"生效并验证: source ~/.zshrc nvm -vWindows 上配 nvm-windows 先看 nvm 目录: nvm root通常输出: C:\Users\<你>\AppData\Roaming\nvm系统环境变量里加两条: NVM_HOME = C:\Users\<你>\AppData\Roaming\nvm NVM_SYMLINK = C:\Program Files\nodejs再把 Path 里追加: %NVM_HOME% %NVM_SYMLINK%新开一个 CMD 窗口,验证: nvm version node -v npm -v常用命令速查 nvm install 22 # 装 Node 22 nvm list # 已装版本 nvm use 22 # 切换版本 nvm alias default 22 # 设默认(macOS/Linux) nvm current # 当前使用版本which node 有输出,但 npm not found 碰到这种情况: $ which node /Users/you/.nvm/versions/node/v22.21.1/bin/node$ which npm npm not found有六种可能,从概率高到低排: 1. 只装了 nvm 没装 Node nvm list # 一片空 nvm install 22 nvm use 222. 版本没激活 nvm current # 显示 none / N/A nvm use 223. PATH 没指向当前 Node echo $PATH # 看有没有 .nvm/versions/node/v22.x.x/bin4. Node 目录里 npm 文件真的没了 ls -lah /Users/you/.nvm/versions/node/v22.21.1/bin/正常应该有 node、npm、npx、corepack。只有 node 说明安装被中断过,重装: nvm uninstall 22 nvm install 225. Windows:nvm-windows 软链坏了(最常见) dir "C:\Program Files\nodejs"看到目录是空的或者链接指向失效版本,重新 nvm use: nvm use 22.19.0之后 where node / where npm 都会有结果。 6. Homebrew 装的 nvm 没加载环境 nvm use default # 或 nvm install --lts一句话总结 nvm 有问题时按这个顺序自检:nvm list → nvm current → which node / which npm → 检查 bin 目录是否完整。95% 的问题都是"没 use 版本"或"软链坏了"。

Python 读取 YAML:safe_load 和转对象访问

Python 里读 YAML 配置文件,标准做法就是 PyYAML。 安装 pip install pyyaml示例配置 config.yaml: server: host: 127.0.0.1 port: 8000database: user: root password: 123456names: - alice - bob基础读取 import yamlwith open("config.yaml", "r", encoding="utf-8") as f: data = yaml.safe_load(f)print(data["server"]["host"]) # 127.0.0.1 print(data["names"]) # ['alice', 'bob']永远用 safe_load,别用 yaml.load。safe_load 拒绝任意 Python 对象反序列化,避开加载不受信配置时的 RCE 风险。 转对象访问(少写引号) 字典下标写多了嫌烦,可以套一层 SimpleNamespace: from types import SimpleNamespace import yamlwith open("config.yaml", encoding="utf-8") as f: data = yaml.safe_load(f)cfg = SimpleNamespace(**data) print(cfg.server) # 但 cfg.server 还是 dict只包一层不够——嵌套的字典还得递归转: from types import SimpleNamespace import yamldef to_obj(d): if isinstance(d, dict): return SimpleNamespace(**{k: to_obj(v) for k, v in d.items()}) if isinstance(d, list): return [to_obj(i) for i in d] return dwith open("config.yaml", encoding="utf-8") as f: cfg = to_obj(yaml.safe_load(f))print(cfg.server.host) # 127.0.0.1 print(cfg.database.user) # root工程项目更推荐用 pydantic.BaseModel 或 dataclass——能顺带做类型校验: from pydantic import BaseModel import yamlclass ServerCfg(BaseModel): host: str port: intclass Config(BaseModel): server: ServerCfg names: list[str]with open("config.yaml", encoding="utf-8") as f: cfg = Config(**yaml.safe_load(f))print(cfg.server.host)输出 YAML 反向写文件同样常用: import yamldata = {"server": {"host": "0.0.0.0", "port": 80}}with open("out.yaml", "w", encoding="utf-8") as f: yaml.safe_dump(data, f, allow_unicode=True, sort_keys=False)allow_unicode=True 中文不会变成 \uXXXX sort_keys=False 保持字段顺序一句话总结 yaml.safe_load(open(..., encoding="utf-8")) 起步,需要结构化访问就 pydantic。写回用 safe_dump + allow_unicode=True。

Python 读写 YAML:pyyaml safe_load / dump 常用选项

Python 读写 YAML 文件用 PyYAML,核心只有两个函数:yaml.safe_load 和 yaml.dump。 安装 pip install pyyaml读取 YAML # config.yaml server: host: 127.0.0.1 port: 8000database: user: root password: secret123names: - alice - bobimport yamlwith open("config.yaml", "r", encoding="utf-8") as f: data = yaml.safe_load(f)print(data["server"]["host"]) # 127.0.0.1 print(data["names"]) # ['alice', 'bob']用 safe_load 而不是 load——load 会执行 YAML 里嵌入的 Python 代码,存在安全风险。 写出 YAML import yamldata = { "server": {"host": "127.0.0.1", "port": 8000}, "database": {"user": "root", "password": "secret123"}, "names": ["alice", "bob"] }with open("output.yaml", "w", encoding="utf-8") as f: yaml.dump( data, f, allow_unicode=True, # 中文不转成 \uXXXX sort_keys=False # 保持原字典顺序 )默认 sort_keys=True,会按字母排序;allow_unicode=False 会把中文转成 \uXXXX——一般都要加这两个参数。 输出为字符串 yaml_str = yaml.dump(data, allow_unicode=True, sort_keys=False) print(yaml_str)修改 YAML 文件(读取后更新) import yamlwith open("config.yaml", encoding="utf-8") as f: cfg = yaml.safe_load(f)cfg["server"]["port"] = 9000 cfg["names"].append("carol")with open("config.yaml", "w", encoding="utf-8") as f: yaml.dump(cfg, f, allow_unicode=True, sort_keys=False)转成对象访问(点号语法) 如果不喜欢 data["server"]["host"],可以转成 SimpleNamespace: from types import SimpleNamespace import yamldef to_obj(d): if isinstance(d, dict): return SimpleNamespace(**{k: to_obj(v) for k, v in d.items()}) if isinstance(d, list): return [to_obj(i) for i in d] return dwith open("config.yaml", encoding="utf-8") as f: config = to_obj(yaml.safe_load(f))print(config.server.host) # 127.0.0.1 print(config.database.user) # root常见坑 名称含冒号的字符串要加引号,否则 YAML 解析出错: # 错误 name: O:# 正确 name: "O:"字段名问题:Clash Meta 等工具要求字段名是 port,不能是 server_port。读取 sing-box 格式(用 server_port)再写入 Clash 配置时记得手动映射: clash_node = { "name": node["tag"], "server": node["server"], "port": node["server_port"], # 注意字段名 "type": node["type"], }直接 yaml.dump(singbox_node) 生成的字段名 Clash 不认,会报 has unset fields: port。

ModuleNotFoundError: 'Crypto' — pycryptodome 与 pycryptodomex 的坑

代码里: from Crypto.Cipher import AES一跑就崩: ModuleNotFoundError: No module named 'Crypto'网上一查装个 pycrypto——别装,那个包 2013 年就停维护了,还有已知漏洞。 两个包,两个命名空间 现在的正主是这两个,装错任何一个都不通用:包名 导入命名空间pycryptodome from Crypto...pycryptodomex from Cryptodome...pycryptodome:兼容老代码,占用 Crypto 顶层名字 pycryptodomex:改名版本,把命名空间挪到 Cryptodome,避免和老 pycrypto 冲突同一个环境里最多装一个。要是老代码用 from Crypto...,就装 pycryptodome。 装到虚拟环境里,别装到系统 Python PyCharm / VSCode 打开项目通常自动激活了虚拟环境(比如 .venv),但你在别的终端 pip install 时可能装到系统 Python 去了: pip install pycryptodome # 装哪儿了?看运气最稳的写法是显式用当前项目的 Python: D:\Projects\myapp\.venv\Scripts\python.exe -m pip install pycryptodome或者: py -m pip install pycryptodome # Windows py launcher python -m pip install pycryptodome # 当前激活的解释器检查装到哪里了 where python python -c "import sys; print(sys.executable)" python -m pip show pycryptodomeLocation 字段就是包所在目录,肉眼比对一下是不是当前项目的 site-packages。 老代码遇到 pycryptodomex 某些镜像里预装的是 pycryptodomex。老代码不想改的话,兼容层写一下: try: from Crypto.Cipher import AES except ImportError: from Cryptodome.Cipher import AES一句话总结 Crypto = pycryptodome,Cryptodome = pycryptodomex,二选一装到当前项目 Python 里。用 python -m pip install 比裸 pip install 稳。

Python pycryptodome、shutil 目录同步与 tqdm 进度条、PyInstaller 打包

pycryptodome:No module named 'Crypto' 安装了 pycryptodome 但运行时报错: ModuleNotFoundError: No module named 'Crypto'原因:pycryptodome 安装后的导入路径是 Crypto(大写C),和旧版 pycrypto 同名,但两者不兼容。虚拟环境里如果同时装了两个就会冲突。 解决: pip uninstall pycrypto pycryptodome pip install pycryptodome导入方式不变: from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad如果想用 Cryptodome 命名空间(与 pycrypto 完全隔离),改装 pycryptodomex: pip install pycryptodomexfrom Cryptodome.Cipher import AES虚拟环境里确认安装: pip list | grep -i crypto # pycryptodome 3.20.0shutil 目录同步加 tqdm 进度条 shutil.copytree 不支持进度回调,需要手动遍历: import shutil from pathlib import Path from tqdm import tqdmdef sync_dir(src: Path, dst: Path): files = [f for f in src.rglob("*") if f.is_file()] dst.mkdir(parents=True, exist_ok=True) for src_file in tqdm(files, desc="复制中", unit="file"): rel = src_file.relative_to(src) dst_file = dst / rel dst_file.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(src_file, dst_file)shutil.copy2 会保留原文件的修改时间和权限位。 覆盖前备份,保留最近 N 个 from datetime import datetimedef backup_and_replace(src: Path, dst: Path, keep=5): if dst.exists(): ts = datetime.now().strftime("%Y%m%d_%H%M%S") backup = dst.parent / f"{dst.name}_bak_{ts}" dst.rename(backup) # 清理旧备份,只保留最近 keep 个 baks = sorted(dst.parent.glob(f"{dst.name}_bak_*")) for old in baks[:-keep]: shutil.rmtree(old) sync_dir(src, dst)PyInstaller 打包后路径检测 -F 打包成单文件 exe 后,__file__ 不再可用,需要用 sys.executable: import sys from pathlib import Pathif getattr(sys, "frozen", False): # 打包环境:exe 所在目录 BASE_DIR = Path(sys.executable).parent else: # 开发环境:脚本所在目录 BASE_DIR = Path(__file__).parentCONFIG_PATH = BASE_DIR / "config.json"sys.frozen 在 PyInstaller 打包环境下为 True,普通 Python 解释器下不存在(getattr 返回 False)。 打包命令: pyinstaller -F -n mytool --noconsole main.py-F:单文件 -n mytool:输出名称 --noconsole:不弹出控制台窗口(GUI 工具用)完整示例:插件目录同步工具 import sys import json import shutil from pathlib import Path from datetime import datetime from tqdm import tqdmBASE_DIR = Path(sys.executable).parent if getattr(sys, "frozen", False) else Path(__file__).parent CONFIG = json.loads((BASE_DIR / "config.json").read_text(encoding="utf-8"))SRC = Path(CONFIG["src"]) DST = Path(CONFIG["dst"])def backup(dst: Path, keep=5): if not dst.exists(): return ts = datetime.now().strftime("%Y%m%d_%H%M%S") dst.rename(dst.parent / f"{dst.name}_bak_{ts}") for old in sorted(dst.parent.glob(f"{dst.name}_bak_*"))[:-keep]: shutil.rmtree(old)def copy_with_progress(src: Path, dst: Path): files = [f for f in src.rglob("*") if f.is_file()] dst.mkdir(parents=True, exist_ok=True) for f in tqdm(files, unit="file"): rel = f.relative_to(src) out = dst / rel out.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(f, out)backup(DST) copy_with_progress(SRC, DST) print("同步完成")config.json: { "src": "C:/Users/用户名/AppData/Roaming/adspower_global/cwd_global/chrome/Default", "dst": "D:/backup/chrome_profile" }