LoginSignup
4
1

新しい 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 というのもあるのだが、これを使うと依存関係の問題が発生してしまう

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 なんだ。老害。

私は以下の様に使い分けている。

  • Completion, Reference 等:vim-lsp
  • Lint, Format:ale

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 + 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.” の欄に pycodestylepyflakes があるので、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 すると [:, :] になってしまう
  • 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 が無くても自動的に判断してくれるけど
  • 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" って表示される様にしちゃったよ。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1