Tool Stack
lint
- pylint
- flake8
- mypy
lsp 通过 publishDiagnostics 能代替或者通过插件的形式代替这些 lint
code style
- pep8,https://pep8.org/
自动格式化工具
- autopep8
- black,争议大
uncompromising,不可配置,比如以下的强制行为:
- 单引号转双引号
- 空行
- yapf, by google auto-formatting + beautify 可选择不同的 style guideline
lsp 通过 codeFormatting 支持格式化
lsp
支持各种 IDE feature,如:
Auto Completion Code Linting Signature Help Go to definition Hover Find References Document Symbols Document Formatting Code folding Multiple workspaces
补完通过传递 ~
CompletionItem~
自动导入(autoImports) 通过 CompletionItem.additionalTextEdits
传递
工具
- [ ] pyright
- [ ] jedi
- [X] pylsp
pyright
pyright 的定位是一个静态类型检测器,与之比较的是 mypy。不过它还带有一个实现 lsp 的服务 pyright-langserver
- 支持自动导入,但初始化貌似没有加载 sys.path 的 module, 必须先 import os 后才行
- 支持手动创建 stub 文件:
pyright --createstub cv2
- 对于额外的
.pyi
文件,比如 cv2 ,只需要存放到项目根目录的typings
文件夹,便能优先识别:
typings ├── cv2 │ └── __init__.pyi
Jedi
- static analysis tool,提供库的功能:
jedi.set_debug_function() # 输出补全列表 jedi.Script('import cv2;cv2.', environment=jedi.get_system_environment('3.10')).complete()
- lsp 功能由 pappasam/jedi-language-server 提供
- 见 Features and Limitations — Jedi 0.18.2 documentation
- 应该是支持 Type Hinting
- 支持的一些文档生成工具的注释风格:
- 对于一些较大的库,如 numpy 加载较慢,可以使用预加载(~preload_module()~)
- 使用 Typeshed 为 stdlib 提供补全
- 要支持 cv2 等 python-type-stubs 需要将 pyi 文件放入 cv2 module 目录下,如 venv 下的 site_packages
pylsp
python-lsp-server 相当于一个实现 lsp 的脚手架,基础功能由 Jedi 提供,包括 Completions、Definitions、Hover、References、Signature Help 和 Symbols。 各个功能通过插件扩展实现。另外一些功能通过默认开启的插件提供,如 pycodestyle、pyflake 提供 diagnostic。
问题
- 经常无响应,导致几乎不可用。估计是这个 ISSUE 描述的问题 https://github.com/python-lsp/python-lsp-server/issues/227
插件
- 插件的发现机制:
On startup, pylsp will automatically discover plugins by querying pkg_resources for entrypoints.
如:
[options.entry_points] pylsp = pylsp_myplugin = pylsp_myplugin.plugin
- 基于 pluggy
缺陷
- 补全速度较慢
安装
为了用上最新的特性,clone 到本地再安装到虚拟环境
git clone https://github.com/python-lsp/python-lsp-server.git
pip install '.[all]' # 安装全部依赖
配置
- rope
- pylsp.plugins.rope_completion.enabled 默认不启用 completion,有 jedi 就够了,保持不启用,不开启也能使用 autoimport
- pylsp.plugins.rope_autoimport.enabled 补全自动 import 需要开启
- pylsp-rope,非自带插件
通过 code action 提供更多 rope refactor 的能力,比如
- Extract method
- Convert lacal variable to field
- Organize import
- …
- mccabe 用于计算循环复杂度 默认开启
- linter 默认是用 pycodestyle、pyflake,不支持 pyproject.toml 配置
- formatter
- 手动禁用 autopep8
pylsp.plugins.autopep8.enabled
- yapf,内置需要手动开启,选择不开启,问题如下:
- 不支持 3.10 =match…case=
- 存在
pyproject.toml
的项目需要先安装toml
yapf 才愿意解析pip install toml
- paradoxxxzero/pyls-isort
接收
textDocument/formatting
通过 isort 对 import 语句进行排序 - python-lsp/python-lsp-black
- 与 isort 、pylint 的兼容需要阅读:Using Black with other tools - Black 22.10.0 documentation
- pycodestyle 兼容需要,项目目录下建立
pycodestyle.cfg
文件,并写入:[pycodestyle] max-line-length = 88
- pycodestyle 兼容需要,项目目录下建立
- 与 isort 、pylint 的兼容需要阅读:Using Black with other tools - Black 22.10.0 documentation
- 手动禁用 autopep8
- pylsp-mypy
用于 lint,通过 pyproject.toml 配置
mypy 不能识别 `pip install –editable` 基于 setuptools 的包,见 #7508
在
pyproject.toml
添加如下解决:[tool.setuptools] zip-safe = false # force setuptools to always install the project as a directory. required by mypy(#7508)
Editor(Emacs)
venv 环境
安装 python-lsp-server- pip install pyright black
lsp-bridge
环境要求:
- pip install epc
elisp 模块与 python 模块 还有一个额外 acm 用于补全
langserver/*.json
的作用:
- python 模块解析
-
Syntax
Shebang
不要写死 python 的路径,而是通过 env 获取 python 的路径:
python3:
#! /usr/bin/env python3
python2 或 python3:
#! /usr/bin/env python
Tips
- 换行,在行尾加
\
- try…except…else 写法
下划线变量
- __all__
函数
参数默认值
- 函数内修改参数默认值必须非常小心地完成。原因是第一次到达函数时,参数的默认值仅被执行一次。此后,相同的值(或可变对象)在后续函数调用中被引用。
Plugin
pluggy
一个 PluginManager 下有多个 hook,hook 由一个 spec 和多个 impl 组成。
hookspec
hookspec = pluggy.HookspecMarker("project_name")
hookspec 是一个 decorator,装饰一个函数相当于声明一个 spec。函数名相当于 spec 的签名,由于一个 hook 只有一个 spec 所以也相当于是 hook 的签名。也就是说同一个 PluginManager 下的 hookspec 不允许重名的。 具体的实现,是通过为被装饰函数添加一个属性来标识该函数是 spec
setattr(
func,
self.project_name + "_spec",
dict(
firstresult=firstresult,
historic=historic,
warn_on_impl=warn_on_impl,
),
)
hookspec 函数并不会被执行, pluggy 只会分析其函数签名。
hookimpl
hookimpl = pluggy.HookimplMarker("project_name")
hookspec 也是一个 decorator,作用与实现与 hookspec 类似。处理默认用函数名作为 hook 签名外,还能通过 specname 属性指定签名。
PluginManager
添加 hookspec(hookspec 等价于 hook):
pm.add_hookspecs(module_or_class)
遍历 module_or_class 的所有属性,找出其中被相同 project_name
的 hookspec 装饰的函数。接着对每个 hookspec 再其属性 pm.hook
创建一个相同签名的函数。
注册 hookimpl(相当于 plugin):
pm.register(module_or_class)
遍历 module_or_class 的所有属性,找出其中被相同 project_name
的 hookimpl 装饰的函数。添加到对应的 hook 中。如果找不到对应的 hookspec 会自动创建一个没有 hookspec 的 hook(HookCaller). 但是如果调用 pm.check_pending()
就会报错。通过设置 optionalhook 可以避免报错。
historic
@hookspec(historic=True)
def myhook(arg1, arg2):
pass
标识可以在 register 之前,可以向改 hook 注册回调,每当一个新的 hookimpl 被注册,就是执行并将结果传给回调。
pm.hook.myhook.call_historic(lambda r: print(f"lambda:{r}"), {'arg1':1, 'arg2':2})
缺点
不支持条件触发。
entry_points
setuptools 也能通过配置 entry_points 实现插件的效果。
以为 plugin 的 pyproject.toml 为例:
[project.entry-points.{entry_point_group}]
{name} = {entry_point}
# 或者
[project.entry-points]
{entry_point_group} = [
n{name} = {entry_point},...
]
host 可以这样调用:
from importlib.metadata import entry_points
plugins_eps = entry_points(group=entry_point_group)
for ep in plugins_eps:
plugin = ep.load()
entry point 的语法见 Entry Points - setuptools
Test
pytest
fixture
- parameter matter! test 函数中的参数就是 fixture
- fixture 的返回是会被缓存的[fn:1],由
scope
控制,默认是 function- 也就是说同一个 test 多次引用同一个 fixture 指向的都是同一个实例
- 更大的 scope 更早执行,比如 session 比 class 早
- autouse 标识所有 test 都依赖的 fixture 同一个 scope 下 autouse 最先执行
- request 是 fixture 的特殊参数,一个对象,可以用于内省测试函数、类或模块上下文
- 比如直接添加 teardown:
request.addfinalizer(delete_user)
- 或者通过
@pytest.mark
为 fixture 提供数据
- 比如直接添加 teardown:
conftest.py
,提供可供整个目录使用的 fixtures fixture 的搜索策略是当前再往上
parametrizing
对于每个参数都都生成一个新的方法
- fixture
每个依赖以下 fixture 的 test 都会分别生成两个 test:
- test[smtp.gmail.com]
- test[mail.python.org]
@pytest.fixture(scope="module", params=["smtp.gmail.com", "mail.python.org"])
def smtp_connection(request):
- test function @pytest.mark.parametrize.
Footnotes
[fn:1] https://docs.pytest.org/en/6.2.x/fixture.html#scope-sharing-fixtures-across-classes-modules-packages-or-session