尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Neovim 配置 Python LSP:pyright 与 pyrefly 双服务器实战指南

发布时间:2026/9/26 20:35:22

资讯中心
01
ARTICLE

Neovim 配置 Python LSP:pyright 与 pyrefly 双服务器实战指南

Neovim 配置 Python LSP:pyright 与 pyrefly 双服务器实战指南
我见过太多把 Neovim 折腾得花里花哨、状态栏文件树配色换了一轮又一轮但打开 Python 文件却依然只有语法高亮的朋友。问题根本不在插件多少而在 LSP——语言服务器协议。编辑器本身只负责编辑代码补全、跳转定义、类型诊断、重命名这些智能行为全部由独立进程里的语言服务器完成。这次我花了一个晚上把 Neovim 的 Python LSP 配置彻底捋顺路线同时覆盖 pyright 和 pyrefly 两个服务器。如果你已经在用 Neovim却总觉得 Python 开发差点意思或者刚想从 VS Code 迁过来这篇可以直接当配置手册用。1. 为什么我最后选了 pyright 和 pyrefly 来救 Python 开发体验1.1 先弄清楚Neovim 的智能到底是谁提供的很多初学 Neovim 的人会有一个误解以为装了 treesitter 就拥有了代码分析能力。实际上 treesitter 做的是语法高亮和结构解析它让编辑器知道这个 token 是变量还是函数但它不会告诉你这个变量类型不匹配这个函数有三个调用方分别在哪。这些能力来自 LSP 协议里的文本同步、诊断推送、跳转请求。LSP 的全称是 Language Server Protocol本质是一条 JSON-RPC 通信通道。语言服务器是一个独立进程编辑器把当前文件内容、光标位置通过协议告诉它它再把补全列表、诊断、定义位置返回给编辑器。Neovim 从 0.5 开始内置了 LSP 客户端意味着不需要任何插件就能跑起一套完整的语言服务流程插件只是帮你省去繁琐的默认参数配置。1.2 市面上的 Python LSP各有各的脾气我简单扫了一圈当前主流的 Python LSP列个表也许更直观服务器开发方实现语言特点适合场景pyrightMicrosoftTypeScript生态最成熟文档全类型推断强大多数项目的首选社区教程多pyreflyMetaRust性能激进并行检查年轻但发展快大型仓库、对保存后诊断速度敏感的人basedpyright社区维护的 pyright 分支TypeScript修复了 pyright 部分顽固问题加了 stricter 选项需要更严厉 lint 的团队jedi-language-server社区基于 jediPython支持 Python 2轻量老项目兼容场景python-lsp-serverPalantir 接手维护Python前身是 pyls可插拔轻量配置但补全能力相对弱我挑 pyright 和 pyrefly 来讲不是别的不好而是这两个刚好代表了两条最典型的路线pyright 是稳pyrefly 是新。而且它们都遵循标准 LSP 协议在 Neovim 里的接入方式几乎一样切换成本极低。你完全可以两个都装上按项目自由切换。1.3 为什么我会同时配两个而不是只锁死一个原因很简单不同项目的痛点不一样。给一个 2000 行的小工具做类型检查pyright 的体验已经足够顺滑报错信息也更细致有的放矢。但到了十万行以上的项目每次保存后等诊断结果的那几秒就非常煎熬这时候 pyrefly 基于 Rust 的并行检查优势就很明显。两个服务器同时配置就像手里同时有电钻和螺丝刀不同场景换着用谁也不碍着谁。提示Neovim 里的 LSP 配置并不绑定某个服务器。只要服务器实现了 LSP 协议你只需告诉 Neovim启动命令是什么、项目根目录怎么判断、哪些文件类型启用剩下的补全、跳转、诊断都是同一套客户端机制。这就是为什么切换 pyright 和 pyrefly 只是几行配置的差别。2. Neovim 侧接入 LSP 前先把三条工作链理顺2.1 三个组件各司其职在写配置之前有必要把 Neovim 侧参与 LSP 的三个部分拆清楚否则你会遇到明明配置了却没反应的情况。第一是内置客户端也就是vim.lsp.*这组 API。它负责和语言服务器进程建立连接管理 buffer 的同步、诊断的回调、补全请求的收发。从 Neovim 0.5 开始就有了但越新版本越稳定建议你用 0.10 及以上。第二是nvim-lspconfig插件。它本身不提供语言能力只是给每个语言服务器准备了一套默认参数比如 pyright 的启动命令、文件类型、root_dir 判断方式。你只要写require(lspconfig).pyright.setup({ ... })它会在你打开.py文件时自动用这些参数拉起服务器。第三是cmp-nvim-lsp。如果你在用 nvim-cmp 做补全菜单这个插件能生成一套标准的 capabilities客户端能力声明告诉语言服务器我支持 snippet、我支持补全项的 resolve。没有这一步补全列表可能弹不出来或者弹出的只有裸词没有函数签名和文档。2.2 一段最基础的 LSP 启动逻辑其实不管 lspconfig 包了多少层底层做的事情就这几行vim.lsp.start({ name pyright, cmd { pyright-langserver, --stdio }, root_dir vim.fs.root(0, { pyproject.toml, setup.py, .git }), })vim.lsp.start拿到cmd和root_dir之后会去启动外部进程建立 stdio 通信然后把这个 buffer attach 上去。lspconfig 做的就是替你把 pyright 的默认启动命令和 root_dir 判定规则填好。所以当你遇到lspconfig 不认识 pyrefly这种问题时完全可以直接vim.lsp.start手动接入远没有想象中复杂。2.3 环境准备清单在动手配置之前先把环境理顺避免后面排查时人麻Neovim 版本nvim --version建议 0.10 以上。0.9 也能跑但部分 API 和 lspconfig 新版本不兼容。Node.js / npm安装 pyright 时需要建议 Node 18。Python3 与 pip安装 pyrefly 和 pyright 的 pip 版时需要。nvim-lspconfig务必保持最新版本。pyrefly 是较新的服务器老版本 lspconfig 很可能还没有内置它的模块。2.4 键位绑定让 LSP 能力真正可用配好服务器不上键位等于买了车不点火。我强烈建议在on_attach回调里做 buffer 级别的键位映射这样每个文件只在自己作用域生效local on_attach function(client, bufnr) local bufopts { noremap true, silent true, buffer bufnr } vim.keymap.set(n, gd, vim.lsp.buf.definition, bufopts) vim.keymap.set(n, K, vim.lsp.buf.hover, bufopts) vim.keymap.set(n, gr, vim.lsp.buf.references, bufopts) vim.keymap.set(n, leaderrn, vim.lsp.buf.rename, bufopts) vim.keymap.set(n, [d, vim.diagnostic.goto_prev, bufopts) vim.keymap.set(n, ]d, vim.diagnostic.goto_next, bufopts) vim.keymap.set(n, leaderca, vim.lsp.buf.code_action, bufopts) end注意buffer bufnr这个参数它把映射限制在当前 buffer 内避免切换文件后键位串掉。如果你不想手动配这么多也可以装nvim-lspconfig官方推荐的 keymap 片段但我的经验是手写一份反而更清晰想改键位时一目了然。3. pyright 配置落地安装、参数、验证一条龙3.1 两种安装方式任选其一pyright 的官方发行方式是 npm 包装完直接有pyright和pyright-langserver两个命令。第二个才是 LSP 启动时要用的。npm install -g pyright如果不想碰 npm 全局也可以走 pipxpipx install pyright这里有个非常容易踩的坑npm 全局安装后命令可能不在当前 shell 的 PATH 里。尤其用 nvm 或 asdf 管理 Node 版本的机器npm 全局 bin 目录经常和系统 PATH 脱节。装完先执行pyright --version验证一下如果提示 command not found查一下npm root -g返回的路径把它的上一层bin目录手动加进 PATH。3.2 lspconfig 里的 pyright setup 完整块我的init.lua里 pyright 这部分配置如下local capabilities require(cmp_nvim_lsp).default_capabilities() require(lspconfig).pyright.setup({ capabilities capabilities, on_attach on_attach, settings { python { analysis { typeCheckingMode basic, diagnosticMode workspace, useLibraryCodeForTypes true, autoSearchPaths true, indexing true, inlayHints { variableTypes true, functionReturnTypes true, parameterNames all, }, }, }, }, })capabilities是补全菜单能否显示 snippet、能否增量同步的关键on_attach用来挂上一节里那组键位。这两项几乎每个 LSP 都要带属于通用模板真正体现差异的是settings。3.3 每个字段背后的逻辑别照抄不思考typeCheckingMode是最重要的开关它有四个等级off、basic、standard、strict。basic只报明显的类型错误适合新项目strict会强制要求所有函数参数和返回值都有类型注解没有注解的地方一律报 warning 甚至 error。我的建议是不要一上来就 strict尤其老项目你会被几万行报错淹没。从我实际经验看basic起步等项目类型注解覆盖率上来之后再升到standard性价比最高。diagnosticMode默认是openFilesOnly也就是只对当前打开的文件做检查。我改成workspace后整个项目的错误都会在保存时汇总跳转诊断非常方便。但代价是首次索引和保存后的诊断延迟变大如果你的项目超过几万行建议还是回到openFilesOnly。useLibraryCodeForTypes决定是否扫描三方库源码来推导类型。开true之后pandas、numpy 这类重库的补全质量和类型提示会明显变好代价是首次索引慢一点。autoSearchPaths让 pyright 自动去找项目里的.venv或venv目录不用手动写解释器路径。indexing控制全量索引开true后跳转定义在大项目里更丝滑。inlayHints是内联提示会在行内显示变量类型推断结果和函数参数名提示看代码时非常直观我个人觉得比装各种装饰插件都实用。3.4 验证配置是否真的生效配置写完重启 Neovim随便打开一个.py文件。先用:LspInfo查看当前 buffer attach 了哪个 client确认列表里出现pyright。接着做三件事验证功能把光标放在某个变量上按K能弹出 hover 说明服务器正常按gd跳到定义处说明文本同步正常故意写一行x abc 1看看有没有红色诊断波浪线。如果有诊断但没有可见标记可能是vim.diagnostic.config没开 virtual_text 和 signs执行下面这段查看或修改vim.diagnostic.config({ virtual_text true, signs true, underline true, })4. pyrefly 切换指南安装、接入与跨工具差异4.1 pyrefly 是什么凭什么值得配pyrefly 是 Meta 开源的类型检查器Rust 实现同时提供 LSP 服务器。它最大的卖点是性能因为底层是 Rust且检查采用并行策略在大型代码库上的诊断速度和全量索引速度都比 pyright 有明显优势。像我们平时写的工程里一个文件动不动几千行pyrefly 保存后几乎秒出诊断这就是我切过去的最直接动力。4.2 安装和启动验证pyrefly 走 PyPI 分发装起来非常简单pip install pyrefly # 或者用 pipx 隔离环境 pipx install pyrefly装完执行pyrefly language-server --help如果能出来参数说明说明命令可用。它的 LSP 启动命令是pyrefly language-server后面接--stdio时配置略有区别但在 Neovim 里通常不需要显式传。4.3 在 Neovim 中接入先查 lspconfig 是否认它较新版本的 nvim-lspconfig 已经内置了 pyrefly 的 server_configuration你可以直接打开这个文件确认~/.local/share/nvim/lazy/lspconfig.nvim/lua/lspconfig/server_configurations/pyrefly.lua如果存在直接 setup 就能用require(lspconfig).pyrefly.setup({ capabilities capabilities, on_attach on_attach, })如果找不到这个文件说明你的 lspconfig 版本太老或者插件管理器还没更新。这种情况下不用慌手动接入非常简单用vim.lsp.start就能拉起vim.api.nvim_create_autocmd(FileType, { pattern python, callback function() local root_dir vim.fs.root(0, { pyproject.toml, setup.py, .git }) if root_dir then vim.lsp.start({ name pyrefly, cmd { pyrefly, language-server }, root_dir root_dir, capabilities capabilities, }) end end, })这段代码相当于手工替 lspconfig 填默认值完全够用。4.4 pyrefly 和 pyright 配置体系的差异pyright 的配置主要通过 LSP 的settings字段传递写在 Neovim 的 setup 里pyrefly 则更倾向于项目内声明式配置也就是在pyproject.toml里写[tool.pyrefly]段落。我的项目里一个典型配置长这样[tool.pyrefly] strict true include [src, tests] exclude [build, dist, venv]你需要留意的是不要假设 pyright 的所有字段在 pyrefly 里同名生效。比如 typeCheckingMode 这种概念pyrefly 有自己的表达方式报错的严肃程度也和 pyright 不完全一致。建议以你安装版本的官方文档为准因为 pyrefly 还在快速迭代版本之间配置键名有变动的可能。我踩过最狠的一次就是按网上旧教程写配置升级后一堆键不认最后还是翻官方文档解决的。5. 实际踩坑记录从进程没起来到补全不弹的排查链路5.1 坑一pyright: command not found现象很明显启动 Neovim 后发现:LspInfo里面没有 pyright 客户端命令行敲pyright也提示找不到。大概率是 npm 全局 bin 路径不在 PATH 里。排查命令which pyright # 看有没有被识别 npm root -g # 看全局 node_modules 路径 echo $PATH # 看 PATH 里有没有对应 bin 目录解决方式两种一是在 shell 配置里把 bin 目录加进 PATH二是不改 PATH直接用npx pyright --version验证后把 lspconfig 的 cmd 改成npx pyright-langserver --stdio。后者改 lspconfig 默认 cmd 的方式不够优雅但胜在不影响系统环境。5.2 坑二进程起来了补全菜单就是不弹这个坑我帮别人排查过好几次。:LspInfo明明显示 pyright 已连接K能用、gd能用偏偏补全菜单不出现。根源多半是 capabilities 没设置对。如果你直接用了vim.lsp.protocol.make_client_capabilities()这种裸生成的 capability语言服务器不会把 snippet 补全等能力返回给你cmp 菜单就只显示纯文本候选甚至不弹。最省事的解决方式local capabilities require(cmp_nvim_lsp).default_capabilities()cmp_nvim_lsp会把 nvim-cmp 支持的所有能力细节填进 capabilities这样服务器返回的补全项才完整。如果你还没装cmp_nvim_lsp先去装它别手写。5.3 坑三pyrefly 连上了但啥也不返回如果 pyrefly 已经被 attach但 hover、诊断、补全全部没反应第一件事不是改配置而是看日志。用:LspLog打开 LSP 日志或者直接看~/.local/state/nvim/lsp.log。日志里最常见的错误是初始化参数解析失败或找不到项目配置这两个都可以归结到 root_dir 上。pyrefly 启动后会以 root_dir 为基准去加载 pyproject.toml如果你在用vim.lsp.start手动配置时没传 root_dir或者 root_dir 指向了错误的目录服务器等于在一个空房子里干活自然不会有结果。确认方式就是检查:LspInfo里显示的 root directory 是不是你的项目根目录。另外如果你同时装了 pyrefly 和 pyright并且两个都通过 lspconfig 注册过同一个 Python 文件可能会被两个 server 同时接管这时候也会出现补全时好时坏的诡异情况。我建议默认只注册一个另一个保留手动启动能力避免双 client 打架。5.4 坑四LSP 看不到虚拟环境autoSearchPaths开启的情况下pyright 会自动去找项目里的.venv但如果你把虚拟环境放在别的位置或者项目是 monorepo 结构自动搜索会失灵。手动指定解释器路径是更可靠的方式settings { python { pythonPath /path/to/your/venv/bin/python, }, }这里特别注意Neovim 里的python3_host_prog和 LSP 用的解释器是两回事。前者是给 Neovim 的远程插件比如一些依赖 python 的 nvim 插件用的运行时后者是语言服务器做类型分析时用的解释器。我见过有人把两个混为一谈改了g:python3_host_prog以为 LSP 就会换解释器结果完全不生效。6. 按项目自动切换 LSP 的最终方案与我的顺手配置6.1 我的选择策略pyproject.toml 里有 pyrefly 就用 pyrefly来回手动切换太麻烦我最后做了一套自动判断逻辑打开 Python 文件时如果项目根目录的pyproject.toml里声明了[tool.pyrefly]就启动 pyrefly否则启动 pyright。这个方式非常符合我的实际使用习惯因为新项目我都在 pyproject 里写明了选哪个服务器。具体代码local function project_prefers_pyrefly(root) if not root then return false end local pyproject root .. /pyproject.toml if vim.fn.filereadable(pyproject) ~ 1 then return false end for _, line in ipairs(vim.fn.readfile(pyproject)) do if line:match(^%[tool%.pyrefly%]) then return true end end return false end vim.api.nvim_create_autocmd(FileType, { pattern python, callback function() local root vim.fs.root(0, { pyproject.toml, setup.py, .git }) for _, client in ipairs(vim.lsp.get_clients({ bufnr 0 })) do if client.name pyright or client.name pyrefly then vim.lsp.stop_client(client.id) end end if project_prefers_pyrefly(root) then vim.lsp.start({ name pyrefly, cmd { pyrefly, language-server }, root_dir root, capabilities capabilities, on_attach on_attach, }) else require(lspconfig).pyright.setup({ capabilities capabilities, on_attach on_attach, settings { python { analysis { typeCheckingMode basic, diagnosticMode workspace, useLibraryCodeForTypes true, autoSearchPaths true, indexing true, }, }, }, }) end end, })这段代码的逻辑是每次打开 Python 文件先停掉当前 buffer 上已有的 pyright 或 pyrefly 客户端再根据项目 pyproject 决定启动哪个。vim.fs.root是 Neovim 0.10 的 API用它判断项目根目录非常干净。如果你只想用 pyright把 pyrefly 相关的条件删掉即可。注意如果你用的是老版本 lspconfig同时又在全局init.lua里提前注册过 pyright上面的 autocmd 里那句stop_client是必须的否则会重复启动两个客户端。6.2 我现在的最终配置一页纸如果你不想折腾自动切换只想有一套能直接用的配置参考这一版假设已安装 pyright、pyrefly、cmp-nvim-lsplocal capabilities require(cmp_nvim_lsp).default_capabilities() local on_attach function(client, bufnr) local bufopts { noremap true, silent true, buffer bufnr } vim.keymap.set(n, gd, vim.lsp.buf.definition, bufopts) vim.keymap.set(n, K, vim.lsp.buf.hover, bufopts) vim.keymap.set(n, gr, vim.lsp.buf.references, bufopts) vim.keymap.set(n, leaderrn, vim.lsp.buf.rename, bufopts) end require(lspconfig).pyright.setup({ capabilities capabilities, on_attach on_attach, settings { python { analysis { typeCheckingMode basic, diagnosticMode workspace, useLibraryCodeForTypes true, inlayHints { variableTypes true, functionReturnTypes true, }, }, }, }, }) -- pyrefly 单独用 autocmd 手动启动 vim.api.nvim_create_autocmd(FileType, { pattern python, callback function() local root vim.fs.root(0, { pyproject.toml, setup.py, .git }) if root and vim.fn.filereadable(root .. /pyproject.toml) 1 and vim.fn.readfile(root .. /pyproject.toml):match(^%[tool%.pyrefly%]) then vim.lsp.start({ name pyrefly, cmd { pyrefly, language-server }, root_dir root, capabilities capabilities, on_attach on_attach, }) end end, })这个小版本的好处是 windows 上也不好乱pyright 走 lspconfig 的全套默认pyrefly 只在特定项目里被手动唤醒。等真需要频繁切换的时候再升级成 6.1 的自动判断版。6.3 我实际用下来的感受我现在的状态是个人小工具项目默认 pyright一个两万多行代码的仓库型项目完整转到 pyrefly。保存后的诊断等待时间是变化最明显的pyrefly 几乎秒出pyright 在同样项目上会有可见的停顿。但 pyright 的报错信息更细致某些边界情况的解释也更好懂平时写小项目我反而更愿意看 pyright 的诊断。配置这件事没有绝对答案。先把 pyright 配稳再给 pyrefly 留一条手动启动的退路是我目前觉得性价比最高的方案。你完全可以按自己的项目规模选边站LSP 的切换成本本来就不高今天不满意明天再改也来得及。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。