新しい Python の静的コード解析ツール「Ruff」というものが出来たらしい。Ruff の紹介は gihyo.jp の記事が詳しいので譲るとして、ともかく Ruff は今後のデファクト・スタンダードになる感じがする。
個人的に感じた Ruff の良い所。
- 全部入り Linter
- flake8, isort, mccabe, pycodestyle, pydocstyle, pylint, pyupgrade 全部に対応しているので、Ruff を入れるだけでこれら全部必要なくなる
- めちゃ速いらしい
- JupyterLab で LSP を使うと、ファイルが大きくなった時に物凄く遅くなるので、期待したい
- pyproject.toml で全部設定できる
- flake8 がいつまでたっても pyproject.toml に対応せずに困っていたので、これは嬉しい
- pyproject-flake8 というのもあるのだが、これを使うと依存関係の問題が発生してしまう
- flake8 がいつまでたっても pyproject.toml に対応せずに困っていたので、これは嬉しい
Formatter は Ruff か Black か
私は現時点(2023年12月)で Formatter としては Ruff ではなく Black を推したい。その理由は3点。
- Formatter は Lint の fix をせずに本当に Format だけして欲しい
- 強制的に行分離させたい
- E3 に対応したい
(ひとつめ)
Ruff で Format すると、Lint でエラーとなっている箇所も自動的に直してしまう。
(デフォルトでは、自動的に変換しても問題ないものだけ)
そしてこれはこんな事態を引き起こす。
- とりあえず
import numpy as np
等と書いておく- 後で numpy を使うものを書こうと思っていた
- うっかり Formatter を起動してしまう
- import 文が消えてしまったので、書き直し
これ、私は良くやってしまう。一旦ざっと書いて、綺麗にするのは Formatter 任せ。そして書いている途中でもとりあえず見栄えを確認したい。なんてことをやるので。
(ふたつめ)
以下の様に関数を書いたとする。
def func(a: int, b:int,) -> None:
return
そして Formatter を起動したら、これを以下の様に直して欲しい。
def func(
a: int,
b: int,
) -> None:
return
これがどうしても Ruff だと出来なかった。
(みっつめ)
def や class 間は2行開ける等の自動空行設定も Ruff は対応していなく、Black でないと出来ない。
(E301, E302 等の、いわゆる E3)
Ruff がもっとバージョンアップしたら、これらができるようになるのかもしれないが、とりあえずこれらの理由で、Formatter は Black を使いたい。幸い、Ruff と Black を併用しても(たぶん)問題無いだろう。
私の Ruff 設定
私は Ruff を以下のように設定している。自戒として、少し厳し目の設定にしているつもり。pyproject.toml での書式なので、ruff.toml を使う人はセクション名を読み替える必要がある。
[tool.black]
line-length = 80
[tool.ruff]
line-length = 80
target-version = "py310"
[tool.ruff.lint]
select = ["F", "E", "W", "C", "N", "UP", "PD", "NPY"]
ignore = ["E203"]
[tool.ruff.lint.mccabe]
max-complexity = 15
- Black は pyproject.toml に対応しているので、一緒に設定できるのは非常に喜ばしい
- Black や Ruff を、なるべくデフォルトの設定のまま使うことが、様々な争いを回避するために重要なんじゃないかと思う
- line-length は 80 一択。普通の大きさのターミナルで横幅に入り切ることが重要だと思う
- Python 3.10 系は既に security update だけのステータスになっていて(ロードマップ)、2026年10月にサポートが終わってしまうが、パッケージによってはまだ 3.11 に対応していないものもあるので、3.10 から抜け出せないでいる
- neologdn のことだったんだけど、あれ、対応した?
- select の "N", "UP", "PD", "NPY" は完全に趣味の領域である。マゾなのかな?
- ignore は、Black を使うと避けられない E203 のみ。マゾなのかな?
- max complexity は昔ながらの 15 で。10 でもマジ無理です。ごめんなさい
Vim で Ruff を使う
うん。VS Code でもなく、Neovim でもなく、Vim なんだ。老害。
私は以下の様に使い分けている。
ale の Ruff 設定
ale の設定は簡単。vimrc に以下の設定をする。
let g:ale_linters = {
\ 'python': ['ruff']
\ }
let g:ale_python_auto_poetry = 1
let g:ale_python_auto_virtualenv = 1
let g:ale_python_ruff_auto_poetry = 1
let g:ale_python_ruff_change_directory = 1
たぶんこれで大丈夫なはず。(無責任)(特に後ろの4行)
Formatter も Ruff を使いたいなら以下。(私は black
と設定しているけど)
let g:ale_fixers = {
\ 'python': ['ruff'],
\ }
vim-lsp の Ruff 設定
ところが vim-lsp に Ruff を認識させるのが難しい。Official な方法は ruff-lsp を使う方らしいので、是非そちらを使いたいのだが、どうにも設定が上手く行かない。かと言って vim-lsp-settings を使うと、ruff-lsp 専用の venv を作ってしまう。そうじゃなくて今いる virtualenv の ruff を使いたいのだ!
仕方がないので、python-lsp-server 経由で、Ruff の python-lsp-server プラグインである python-lsp-ruff を使う。
virtualenv に ruff, python-lsp-ruff, python-lsp-server を入れて、vimrc には以下の設定をする。
let g:python_pylsp_prog = $VIRTUAL_ENV . '/bin/pylsp'
let g:python_ruff_prog = $VIRTUAL_ENV . '/bin/ruff'
augroup PythonLanguageServer
if executable(g:python_pylsp_prog)
autocmd!
autocmd User lsp_setup call lsp#register_server({
\ 'name': 'pylsp',
\ 'cmd': {server_info -> [g:python_pylsp_prog]},
\ 'whitelist': ['python'],
\ 'workspace_config': {
\ 'pylsp': {
\ 'plugins': {
\ 'ruff': {
\ 'enabled': v:true,
\ 'executable': g:python_ruff_prog
\ },
\ 'autopep8': {
\ 'enabled': v:false
\ },
\ 'yapf': {
\ 'enabled': v:false
\ }
\ }
\ }
\ }
\ })
endif
augroup END
Jedi 関係は使うので disable にしてはダメ。
python-lsp-ruff はデフォルトで pycodestyle, pyflakes, mccabe を disable にするので、明示的に disable にするのは autopep8 と yapf だけで良い筈。
Vim でも Ruff で Format(Lint fix)する
Formatter は Black を使うようにしたが、Ruff で見つけた Lint Error を自動的に fix したい場合もあるだろう。その場合は vim-lsp 経由で Ruff の Format をすれば良い。
:LspCodeAction source.fixAll
この vim コマンドを実行すると、Ruff の Format が走る。keyboard shortcut 等を設定すれば、いつでも簡単に実行できるね!
JupyterLab で Ruff を使う
この場合も Linter は2通りの設定方法が考えられる。
- jupyterlab-lsp + ruff-lsp + ruff
- jupyterlab-lsp + python-lsp-server + python-lsp-ruff + ruff
どちらが良いかを、設定ファイルの有無で考えてみる。
- jupyterlab-lsp + ruff-lsp + ruff
- jupyterlab-lsp を ruff-lsp に対応させるため、
jupyter_server_config.d/ruff-lsp.json
等の json ファイルを作り、設定させる必要がある - ruff-lsp が ruff の設定を認識するために、どこかに pyproject.toml か ruff.toml を配置する必要がある(たぶん)
- jupyterlab-lsp を ruff-lsp に対応させるため、
- jupyterlab-lsp + python-lsp-server + python-lsp-ruff + ruff
- jupyterlab-lsp は python-lsp-server (pylsp) に標準で対応しているため、特に設定する必要が無い
- python-lsp-server は ruff の実行バイナリや pyproject.toml を自動的に検索してくれるので、普通にプロジェクトの pyproject.toml を認識してくれる
- python-lsp-ruff の設定項目は(ruff-lsp より)多いので、それだけでそこそこの設定が出来てしまう
こうやって挙げてみると、断然に後者である。(ruff-lsp がバージョンアップすれば状況は変わるかもしれないが)
私は pipx に JupyterLab を入れる派なので、以下のコマンドでインストールする。
pipx install jupyterlab
pipx inject jupyterlab pip
pipx runpip jupyterlab install black ruff jupyterlab-lsp python-lsp-server python-lsp-ruff jupyterlab-code-formatter
普通に仮想環境等に入れる場合は、以下のコマンドで良いだろう。
pip install jupyterlab black ruff jupyterlab-lsp python-lsp-server python-lsp-ruff jupyterlab-code-formatter
ちなみに上記にしれっと入れてしまっているが、JupyterLab での Formatter の設定は jupyterlab-code-formatter 一択である。
jupyterlab-lsp の Ruff 設定
何も設定しなくてもインストールだけで何となく動いてしまうのだが(笑)。
残念ながら JupterLab の Settings Editor では設定できないので、json ファイルを直接編集することになる。
- JupyterLab を立ち上げて、メニューの Settings から Settings Editor を開く
- 左ペインから
Language Servers
の項目を選び、右ペインで "Available" となっているpylsp
をクリック - ”Settings to be passed to python-lsp-server (pylsp) in workspace/didChangeConfiguration notification.” の欄に
pycodestyle
とpyflakes
があるので、pyflakes
を選ぶ - デフォルトの設定が json に丸っと吐き出される
- さらに右上の
JSON Settings Editor
をクリックすると、その内容を見ることができる
この json ファイルは ~/.jupyter/lab/user-settings/@jupyter-lsp/jupyterlab-lsp/plugin.jupyterlab-settings
という場所に置かれているので、それを直接エディタで編集する。
(もちろん JupyterLab の JSON Settings Editor で編集してもいいけど、ちょっと(かなり)やりにくい)
追加・変更した箇所だけ書き出してみる。
{
"language_servers": {
"pylsp": {
"serverSettings": {
"pylsp.configurationSources": ["pyflakes"],
+ "pylsp.plugins.ruff.enabled": true,
+ "pylsp.plugins.ruff.ignore": ["E203", "E402"],
~ "pylsp.plugins.mccabe.enabled": false,
~ "pylsp.plugins.pycodestyle.enabled": false,
~ "pylsp.plugins.pyflakes.enabled": false,
~ "pylsp.plugins.yapf.enabled": false,
},
"configuration": {
"pylsp.configurationSources": ["pycodestyle"],
+ "pylsp.plugins.ruff.enabled": true,
+ "pylsp.plugins.ruff.ignore": ["E203", "E402"],
~ "pylsp.plugins.mccabe.enabled": false,
~ "pylsp.plugins.pycodestyle.enabled": false,
~ "pylsp.plugins.pyflakes.enabled": false,
~ "pylsp.plugins.yapf.enabled": false,
}
}
}
}
先頭に +
が付いている行が追加、先頭に ~
が付いている行が変更。
ここでは ignore の設定しかしてないけど、Ruff のその他の細かい設定は、プロジェクトの pyproject.toml にて行う。
先にも書いたが、python-lsp-server は実行バイナリや pyproject.toml を自動検出してくれるので、実行しているプロジェクトの pyproject.toml を設定すれば良い。
なぜここで ignore を設定しているのかは、次の章。
避けられない PEP8 違反と Ruff の ignore
Black や JupyterLab を使っていると、どうしても避けられない PEP8 違反が4つある。
- E203: Whitespace before ':'
- 例)Black で
[:,:]
を Format すると[:, :]
になってしまう
- 例)Black で
- E302: Expected 2 blank lines, found 0
- 例)JupyterLab で各セルにひとつの関数を書き、ipynb をまるっとチェックした時、関数間の空行は0
- E402: Module level import not at top of file
- 例)JupyterLab で
import matplotlib
の前に%matplotlib inline
という Magic Command を宣言しないと、matplotlib の backend がちゃんと inline にならない- 今の matplotlib は賢くなっているので、Magic Command が無くても自動的に判断してくれるけど
- 例)JupyterLab で
- W503: Line break occurred before a binary operator
- 例)Black は、長い式の時に2項演算子の前で改行する
しかし、E302, W503 は Ruff が対応してないので、そもそも ignore で設定できない。
(何が ignore で設定できるかは ruff --ignore --help
で確認できる。)
(E302 は Black でフォローする)
jupyterlab-lsp の設定で ignore に追加したいのは、pyproject.toml(通常の Python ファイル)では無視する必要が無いけど、JupyterLab では無視したい項目なので、E402 だけを追加すれば良いということになる。
(けれどもまぁ念の為、E203 も入れてしまっているのだけれど)
jupyterlab-code-formatter の Ruff 設定
jupyterlab-code-formatter は Ruff をサポートしているので、インストールするだけで、そしてちょっとした設定を追加するだけで動く。
- JupyterLab を立ち上げて、メニューの Settings から Settings Editor を開く
- 左ペインから
JupyterLab Code Formatter
を選ぶ - 右ペインの "default formatter" で、python に
black
を設定する- ここで
ruff
を設定することもできるし、python-0 にruff
, python-1 にblack
を設定することで、ruff した後に black する、なんてこともできる
- ここで
- 右ペインの一番下に "Ruff Config" があるので、そこの args に以下を追加する
-
--select=F,E,W,N,UP,PD,NPY
- 修正したいルール(さすがに
C
は fix できない)
- 修正したいルール(さすがに
-
--ignore=E203,E402
- 無視したい項目(上記参照)
-
--target-version=py310
- お使いの Python バージョンに合わせて
-
--unsafe-fixes
- いっそのこと、思い切ってやっちゃってくれ!(設定しなくて良い)
-
--silent
- めちゃくちゃ重要
-
jupyterlab-code-formatter の Ruff 対応実装を見ると、stderr に何か文字が吐き出されると、エラーが発生したと思って logger にメッセージを出して、出力は元のままにしている。
(そもそもこの logger は JupyterLab の logger でも何でも無いので、JupyterLab の Log Console を見ても何も出てこない!)
しかし jupyterlab-code-formatter が呼び出している ruff コマンド(ruff --fix-only -
(最後のハイフンは subprocess から得られる stdin を入力とするという意味))は、何か変更すると stderr に "fix N Errors" という結果を御親切にも吐き出すので、これをエラーと勘違いしてしまう!
そこで --silent
をつけて、この stderr への出力を抑制することで、まともに Ruff による Format が出来るようになる。
これを理解するのに丸一日かかったよ・・・
JupyterLab でも Ruff で Format(Lint fix)する
python の default formatter として black
を指定すると、jupyter-code-formatter のボタン(◯の斜めにチョロっと生えてるやつ)を押すと ipynb 全体を Black で Format する。
ほぼほぼこれを使うと思うのだが、JupyterLab の Edit メニューにも "Apply Black Formatter" と "Apply ruff Formatter" があり、これが実はセル毎に black, ruff で Format するものだ。
すなわち、セルを選び "Apply ruff Formatter" をすれば、Ruff で Format 出来る。(ipynb 全体は出来ないけど)
ちなみにわざわざメニューから呼び出さなくても、Command Palette(ctrl-shift-c)からも呼び出せる。
だから Formatter として Black を使う場合も、jupyterlab-code-formatter に Ruff の設定はしておいた方が良い。
最後に
それにしても何で jupyterlab-code-formatter さんは Black は "Apply Black Formatter" ってしてるのに、Ruff は "Apply ruff Formatter" ってしてるのかなぁ。思わずソースコードを書き換えて "Apply Ruff Formatter" って表示される様にしちゃったよ。