1. 为什么要在 Windows 上折腾 Emacs 写 Python
很多人第一次听到"用 Emacs 写 Python"的反应都是:这不是自找麻烦吗?VS Code 不香吗?PyCharm 不香吗?我一开始也是这么想的,直到我在一台老旧的 Windows 笔记本上同时开着三个编辑器做对比测试,才发现 Emacs 在特定场景下的优势确实没法替代——启动快、内存占用低、键盘流操作一旦形成肌肉记忆,写代码的连贯性会明显提升。但前提是,你得把环境配好,否则默认状态下的 Emacs 写 Python 体验确实很糟糕:没有补全、没有语法检查、没有格式化,连虚拟环境都识别不了。
这篇文章要解决的问题很具体:在 Windows 系统下,把 Emacs 打造成一个能用的 Python 开发环境。核心涉及三块内容——Virtualenv 虚拟环境的接入、Elpy 提供的检查/补全/格式化能力、以及 Windows 平台特有的路径和编码坑。适合已经会用基本 Emacs 操作(至少知道怎么打开文件、保存、执行命令)但还没配过 Python 开发环境的读者,也适合从 Linux/macOS 迁移到 Windows 后发现配置全失效的老用户。
我自己的主力环境是 Windows 10 + Emacs 27.2 + Python 3.9,中间踩过的坑包括:虚拟环境路径识别失败、jedi 补全卡顿、black 格式化后中文注释乱码、flycheck 报错信息截断等等。下面把这些经验完整拆开讲,每一步都说明为什么这么做,以及不这么做会出什么问题。
2. 环境准备与工具选型思路
2.1 为什么选 Elpy 而不是自己拼装
Emacs 的 Python 支持方案大致分两派:一派是"自己拼装",用python-mode加company加flycheck加blacken分别配置;另一派是用elpy这种集成包一次性搞定。我两种都试过,最后留在 Elpy 上,原因很实际。
自己拼装的好处是可控,每个组件都能单独调参,但坏处是组件之间的衔接要自己处理。比如company的补全后端要手动接jedi,flycheck的 checker 要手动指定flake8或pylint,格式化又要单独配black的调用方式。这套东西在 Linux 上配一次能管很久,但在 Windows 上,每个组件的路径处理方式都不一样,调试成本会成倍上升。
Elpy 本质上就是把这些组件打包好了,它内部集成了jedi(补全)、flycheck(检查)、black/autopep8(格式化)、rope(重构)等,并且提供了统一的开关。你不需要关心底层谁调谁,只需要告诉 Elpy "用哪个虚拟环境"和"用哪个格式化工具"就行。对于 Windows 用户来说,这能省掉大量路径调试的时间。
注意:Elpy 并不是没有缺点。它的默认配置偏保守,补全触发有延迟,大项目里
jedi偶尔会卡。后面我会讲怎么调这些参数。
2.2 软件版本与安装顺序
版本选择上,我建议遵循"中间版本最稳"的原则。Emacs 不要用太新的开发版,也不要用过老的 24.x,27.x 或 28.x 是比较稳妥的选择。Python 方面,3.8 到 3.10 都可以,3.11 之后部分依赖包的兼容性还在追赶,如果你不是必须用新特性,3.9 是最省心的。
安装顺序很重要,顺序错了会导致后续步骤反复返工:
- 先装 Emacs,装完后确认
emacs --version能在命令行跑通 - 再装 Python,安装时勾选"Add Python to PATH"
- 然后装虚拟环境工具
virtualenv(或直接用内置的venv) - 最后在 Emacs 里配置包管理器和 Elpy
为什么把 Emacs 放第一步?因为 Emacs 的包管理依赖网络和配置文件路径,如果 Python 先装好了但 Emacs 配置没通,你会分不清是哪个环节出的问题。先把 Emacs 跑通,再逐步接入 Python 相关的东西,排查起来清晰得多。
2.3 包管理器与镜像源配置
Emacs 默认的包源在国外,国内直接拉包经常超时。我试过几个国内镜像,速度和稳定性差异挺大。配置方式是在init.el里设置package-archives:
(require 'package) (setq package-archives '(("gnu" . "https://elpa.gnu.org/packages/") ("melpa" . "https://melpa.org/packages/"))) (package-initialize)如果你发现拉包特别慢,可以临时换成国内镜像地址,但要注意镜像同步有延迟,某些新包可能拉不到。我的做法是平时用官方源,遇到拉不动的包再临时切换,配完就换回来,避免长期依赖镜像导致版本滞后。
提示:
package-initialize这行必须放在配置文件靠前的位置,否则后面require的包会找不到。我见过有人把它写在文件末尾,结果所有包都加载失败,排查了半天。
3. Virtualenv 虚拟环境的接入细节
3.1 虚拟环境到底解决了什么问题
先说清楚为什么非要接虚拟环境。Python 的项目依赖是全局共享的,如果你同时做两个项目,一个要requests 2.25,一个要requests 2.31,全局装只能二选一。虚拟环境就是给每个项目一个独立的包目录,互不干扰。
在命令行里用虚拟环境很简单,激活一下就行。但在 Emacs 里,问题就来了:Emacs 启动时用的是系统默认的 Python 解释器,它不知道你当前项目用的是哪个虚拟环境。如果不做配置,你在 Emacs 里补全出来的库可能是全局的,跑代码时又报ModuleNotFoundError,非常迷惑。
Elpy 提供了elpy-rpc-virtualenv-path这个变量来指定虚拟环境路径,但它的行为在不同版本里不太一样。有的版本支持'current表示自动跟随当前激活的环境,有的版本必须写死绝对路径。我实测下来,最稳的方式是显式指定路径,不依赖自动检测。
3.2 虚拟环境路径的写法与常见错误
Windows 下虚拟环境的目录结构和 Linux 不同。Linux 是venv/bin/python,Windows 是venv\Scripts\python.exe。这个差异导致很多从 Linux 抄来的配置在 Windows 上直接失效。
配置示例:
(setq elpy-rpc-virtualenv-path "C:/Users/yourname/projects/myproject/venv")注意这里用的是正斜杠/,不是反斜杠\。Emacs 的 Lisp 字符串里反斜杠是转义字符,你写"C:\Users\..."会把\U当成转义序列,直接报错。要么用正斜杠,要么用双反斜杠\\。我推荐正斜杠,Windows 的 API 本身也接受正斜杠,写起来清爽。
另一个常见错误是路径末尾带了Scripts。elpy-rpc-virtualenv-path要的是虚拟环境的根目录,不是Scripts目录。你写.../venv/Scripts的话,Elpy 会去找.../venv/Scripts/Scripts/python.exe,自然找不到。
3.3 让 Emacs 自动识别项目虚拟环境
写死路径的问题是换个项目就要改配置。更优雅的做法是用.dir-locals.el或者pyvenv包来做项目级配置。
pyvenv是一个专门管理虚拟环境的包,它能自动检测项目根目录下的venv、.venv、env等目录并激活。配置很简单:
(require 'pyvenv) (pyvenv-mode 1)装好之后,你打开项目里的.py文件,pyvenv会自动搜索上层目录里的虚拟环境并激活。这样 Elpy 的 RPC 进程也会跟着用这个环境,补全和检查都对了。
但pyvenv有个坑:它搜索虚拟环境的规则是固定的,如果你的虚拟环境目录名不叫venv或.venv,它找不到。这时候可以在项目根目录放一个.dir-locals.el:
((python-mode . ((pyvenv-activate . "C:/path/to/your/env"))))注意:
.dir-locals.el修改后需要重新打开文件才生效,而且 Emacs 会问你"是否应用这些变量",第一次要手动确认。如果你在配置文件里设了enable-local-variables为nil,这些配置会被忽略,记得检查。
4. Elpy 的检查、补全与格式化配置
4.1 Elpy 初始化与依赖安装
Elpy 装好之后,第一次启动会提示你安装 RPC 依赖。这个依赖是装在 Python 环境里的,不是 Emacs 包。安装命令是:
M-x elpy-config然后按提示点"Install"或者手动在虚拟环境里跑:
pip install jedi rope flake8 autopep8 black yapf这几个包各司其职:jedi负责补全和跳转,rope负责重构,flake8负责语法和风格检查,autopep8/black/yapf负责格式化。你可以只装需要的,但建议全装,反正体积不大。
elpy-config这个命令非常有用,它会显示当前 Elpy 的状态:用的哪个 Python、哪个虚拟环境、哪些依赖装了哪些没装。配置出问题时,第一件事就是跑这个命令看状态,比瞎猜快得多。
4.2 补全:jedi 的触发时机与性能调优
Elpy 默认的补全触发方式是手动触发,按M-TAB或者C-M-i。很多人觉得不好用,想改成自动弹出。改法是:
(setq elpy-company-backend '(company-elpy-jedi)) (add-hook 'python-mode-hook 'company-mode)但自动补全有个性能问题:jedi每次触发都要分析整个项目的符号表,大项目里会明显卡顿。我试过在一个有几千个文件的项目里开自动补全,打字延迟能到半秒,完全没法用。
解决办法是限制jedi的搜索范围。Elpy 有个变量elpy-company-backend可以配置,但更有效的是在项目根目录放.jedi配置文件,或者用elpy-rpc-timeout控制超时:
(setq elpy-rpc-timeout 2)超时设短了,卡的时候会直接放弃补全而不是一直等。我一般设 2 秒,超过就说明这个补全不值得等。
另一个技巧是关掉不必要项目的索引。jedi默认会索引虚拟环境里的所有包,如果你装了几百个包,索引会很大。可以在jedi配置里排除site-packages,只索引项目自己的代码。
4.3 检查:flycheck 与 flake8 的配合
Elpy 默认用flycheck做实时检查,后端是flake8。flake8会检查语法错误、未使用的导入、行太长、命名不规范等等。默认配置下,每次保存文件就会跑一次检查,错误会显示在行尾和底部。
flake8的默认规则比较严格,行长度限制是 79 字符。如果你习惯 120 字符,需要配置。配置方式是在项目根目录放setup.cfg或.flake8:
[flake8] max-line-length = 120 ignore = E203, W503 exclude = .git,__pycache__,venvE203和W503这两个规则和black格式化有冲突,如果你用black,建议忽略掉,否则会出现"格式化后反而报错"的诡异情况。
提示:
flycheck的报错信息默认显示在 minibuffer,长信息会被截断。可以装flycheck-pos-tip或者用flycheck-error-list查看完整列表。我习惯绑定一个快捷键直接打开错误列表,比一行行看快。
4.4 格式化:black 与 autopep8 的取舍
格式化工具的选择上,black和autopep8是两大主流。black的风格更激进,会强制统一引号、换行、空格,几乎不给自定义空间;autopep8更保守,只修 PEP8 违规的地方,不动其他格式。
我的建议是:新项目用black,老项目用autopep8。新项目没有历史包袱,black的统一风格能省掉很多团队争论;老项目如果已经有一套风格,black一跑会把整个文件重排,diff 会大到没法 review。
Elpy 里切换格式化工具:
(setq elpy-formatter 'black)或者'autopep8、'yapf。设好之后,M-x elpy-format-code就会用指定的工具格式化当前缓冲区。
black在 Windows 上有个编码坑:如果你的文件里有中文注释,black默认用 UTF-8 读写,但 Windows 的默认编码可能是 GBK,导致格式化后中文变乱码。解决办法是在文件头加编码声明,或者确保black版本在 21.12 以上(新版本对编码处理更稳)。
5. 完整实操流程与关键配置
5.1 从零开始的配置步骤
假设你刚装好 Emacs 和 Python,下面是从零到能用的完整流程。
第一步,创建配置文件。Windows 下 Emacs 的配置目录通常在C:\Users\你的用户名\AppData\Roaming\.emacs.d\,主配置文件是init.el。如果目录不存在就手动建。
第二步,写入包管理配置:
(require 'package) (setq package-archives '(("gnu" . "https://elpa.gnu.org/packages/") ("melpa" . "https://melpa.org/packages/"))) (package-initialize) (unless package-archive-contents (package-refresh-contents))第三步,安装并配置 Elpy:
(unless (package-installed-p 'elpy) (package-install 'elpy)) (elpy-enable)第四步,配置虚拟环境。这里用pyvenv做自动检测:
(unless (package-installed-p 'pyvenv) (package-install 'pyvenv)) (require 'pyvenv) (pyvenv-mode 1)第五步,配置补全和格式化:
(setq elpy-company-backend '(company-elpy-jedi)) (setq elpy-formatter 'black) (setq elpy-rpc-timeout 2)第六步,重启 Emacs,打开一个.py文件,跑M-x elpy-config检查状态。如果显示所有依赖都装了,虚拟环境也识别对了,就基本能用了。
5.2 参数计算:超时与索引范围的权衡
elpy-rpc-timeout这个参数值得单独说一下,因为它直接影响使用体验。这个值的单位是秒,表示 Elpy 等待 RPC 进程返回结果的最长时间。
设太小,补全经常超时失败,体验断断续续;设太大,卡的时候会一直等,打字都打不了。怎么定这个值?我的方法是先设一个较大的值(比如 5 秒),然后在实际使用中观察:如果补全基本秒回,说明可以调小到 1-2 秒;如果经常要等 3 秒以上,说明jedi索引太重,应该先优化索引范围而不是加大超时。
索引范围的优化,核心是排除site-packages。jedi默认会索引虚拟环境里的所有第三方包,但大部分时候你补全的是自己项目的代码和少数几个常用库。可以在项目根目录建.jedi/jedi.conf:
[settings] auto_import_modules = []或者在 Elpy 配置里限制:
(setq elpy-company-backend '(company-elpy-jedi)) (setq jedi:environment-root "path/to/venv")实测下来,排除掉不必要索引后,补全响应能从 1 秒以上降到 200 毫秒以内,差别非常明显。
5.3 实操现场:一次完整的配置调试记录
我拿一台全新的 Windows 机器做了一次完整配置,记录下关键节点和遇到的问题。
装完 Emacs 27.2 和 Python 3.9 后,写入上面的配置,重启。打开一个测试文件test.py,内容就一行import requests。此时requests下面有红色波浪线,说明flycheck在报错——因为全局环境里没装requests。
跑M-x elpy-config,发现虚拟环境显示的是系统 Python,不是项目的venv。原因是pyvenv没找到虚拟环境目录,因为我的虚拟环境叫env而不是venv。在项目根目录加了.dir-locals.el指定路径后,重新打开文件,elpy-config显示虚拟环境正确了,红色波浪线也消失了。
接着测试补全,输入requests.后按M-TAB,弹出补全列表,但等了大概 1.5 秒。把elpy-rpc-timeout从默认的 10 秒改成 2 秒,同时排除site-packages索引,再测,响应降到 300 毫秒左右。
最后测试格式化,写了一段缩进混乱的代码,跑M-x elpy-format-code,black把代码整理得整整齐齐。但中文注释变成了乱码,检查发现是black版本太老,升级到最新版后问题解决。
整个过程大概花了 40 分钟,其中一半时间花在虚拟环境路径的排查上。这也是我建议把虚拟环境配置放在最前面做的原因——它是后面所有功能的基础,基础不对,补全和检查都会出问题。
6. 常见问题与排查技巧实录
6.1 补全不工作或补全错误
补全问题是最常见的,表现有好几种:完全不弹、弹出来是错的、弹出来卡半天。排查思路按这个顺序走。
先看elpy-config里jedi装了没。没装的话补全肯定不工作,装上就行。装了还不行,看虚拟环境对不对。虚拟环境错了,jedi索引的是全局包,你项目里的模块自然补不出来。
如果虚拟环境对、jedi也装了,但补全还是错,那可能是jedi的缓存问题。jedi会缓存索引结果,项目结构变了但缓存没更新,补全就会用旧数据。删掉缓存目录(通常在~/.cache/jedi或虚拟环境下的.jedi)重启 Emacs 即可。
卡顿问题前面讲过,核心是限制索引范围。补充一个技巧:如果你只是偶尔需要补全,可以把触发方式改回手动,平时不弹,需要时按一下,这样完全不影响打字流畅度。
6.2 flycheck 报错但代码能跑
这种情况通常是flake8的规则太严,把风格问题当错误报了。比如行太长、变量名不符合规范,这些不影响运行但会标红。
解决办法是调整flake8配置,把不关心的规则加到ignore里。但要注意,不要一股脑全忽略,那样检查就失去意义了。我的做法是保留语法错误和未定义变量这类硬错误,忽略纯风格问题。
另一个可能是flake8用的 Python 版本和项目不一致。比如项目用 Python 3.9 的语法,但flake8用 Python 3.6 跑,遇到新语法就报错。检查elpy-config里flake8的路径,确保它在虚拟环境里。
6.3 格式化后代码反而报错
这个问题的根源通常是格式化工具和检查工具的规则冲突。最典型的是black和flake8的E203、W503冲突。black的换行风格会触发flake8的这两个规则,导致格式化后flycheck报一堆错。
解决方法是前面说的,在flake8配置里忽略这两个规则。如果你用的是autopep8,冲突会少一些,但autopep8的格式化不如black彻底,有时候需要跑两次才能完全整理好。
还有一个坑是格式化工具版本不一致。团队里有人用black 22.x,有人用black 23.x,格式化结果会有细微差异,导致 diff 里出现无意义的改动。建议在项目里锁定格式化工具版本,写进requirements-dev.txt。
6.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方式 |
|---|---|---|---|
| 补全完全不弹 | jedi 未安装 | 跑 elpy-config 看依赖 | pip install jedi |
| 补全结果错误 | 虚拟环境不对 | elpy-config 看环境路径 | 配置 pyvenv 或 dir-locals |
| 补全卡顿 | 索引范围太大 | 观察响应时间 | 排除 site-packages |
| flycheck 误报 | flake8 规则太严 | 看报错内容 | 配置 ignore 规则 |
| 格式化乱码 | 编码不匹配 | 检查文件编码 | 升级 black 或加编码声明 |
| 格式化后报错 | 规则冲突 | 对比报错规则 | 忽略 E203/W503 |
| 虚拟环境不识别 | 目录名不匹配 | 看 pyvenv 搜索规则 | 用 dir-locals 指定 |
提示:这张表建议存下来,遇到问题先对照排查,能省掉大量搜索时间。我自己的经验是,90% 的问题都出在虚拟环境路径和依赖安装这两块,先把这两个确认了,剩下的都好办。
7. 我踩过的坑与独家经验
配置这套环境的过程中,有几个坑是文档里不会写、但实际一定会遇到的,单独拎出来说。
第一个坑是 Windows 的路径分隔符。Emacs 的 Lisp 字符串里,反斜杠是转义字符,你从 Windows 资源管理器复制路径粘贴进去,大概率会报错。我的习惯是永远用正斜杠,不管是配置文件还是命令行,正斜杠在 Windows 上都能正常工作,而且不会有转义问题。
第二个坑是虚拟环境的Scripts目录。前面提过,但值得再强调一次:elpy-rpc-virtualenv-path要的是虚拟环境根目录,不是Scripts。这个错误太常见了,我见过好几个人卡在这里,以为是 Elpy 的 bug,其实是路径多写了一层。
第三个坑是black的编码问题。Windows 中文用户的文件里经常有中文注释,老版本black处理不好,格式化后乱码。升级到最新版基本能解决,但如果你的项目必须用老版本,就在文件头加# -*- coding: utf-8 -*-,能缓解大部分问题。
第四个坑是 Emacs 启动时的包加载顺序。package-initialize必须在所有require之前,elpy-enable必须在pyvenv-mode之后。顺序错了,轻则功能不生效,重则 Emacs 启动报错。我建议配置文件按"包管理 → 环境管理 → 功能包"的顺序组织,逻辑清晰,也不容易出错。
最后一个经验是关于调试方法的。配置出问题时,不要一上来就改配置,先跑M-x elpy-config看状态,再开*Messages*缓冲区看日志。Elpy 的报错信息基本都会打到*Messages*里,比在 minibuffer 里一闪而过的提示有用得多。我排查虚拟环境问题时,就是靠*Messages*里的路径信息定位到pyvenv搜索失败的。
这套配置我用了大半年,中间换过三个项目,每次新项目只需要建好虚拟环境、放一个.dir-locals.el,其他都不用动。稳定性上,日常写代码没出过什么大问题,偶尔补全卡一下,重启 Emacs 就好。如果你也在 Windows 上用 Emacs 写 Python,希望这些经验能帮你少走点弯路。