Ruff は、Python のリンター(コードの問題を見つける)とフォーマッター(書式をそろえる)を1つにまとめたツールです。 flake8・isort・black・pyupgrade などの役割を、1つのコマンドと1つの設定(pyproject.toml)で置き換えられます。Rust で書かれていて速いのが特徴です。

pip install ruff                 # または uv add --dev ruff
ruff check .                     # リント(問題を表示)
ruff check . --fix               # 安全な自動修正をかける
ruff format .                    # 書式をそろえる(black 互換)

最初に知っておきたいのは、0.16.0(2026年7月)で既定のルールが 59 個から 413 個に増えたことです。 0.17.0 で確かめると、何も設定しない状態で 407 個のルールが有効でした。以前の記事の「既定は E と F だけ」という説明は、今の版には当てはまりません。

コマンド 役割 置き換える主なツール
ruff check リント flake8(+bugbear などのプラグイン)、pyupgrade、autoflake
ruff check --select I --fix import の並べ替え isort
ruff format フォーマット black

確認環境:Ruff 0.17.0(pip で導入)、Python 3.11、Linux。比較に black 26.10.1・isort 9.0.2・flake8 7.4.1 を使いました。この記事の出力は、サンプルコードに対して実際にコマンドを実行した結果です。設定項目と既定値は ruff config の出力と Ruff の公式ドキュメント、0.16.0・0.17.0 のリリースノートで確かめました(確認日 2026年10月13日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Ruffの使い方|pyproject.toml設定とルール一覧」

インストール

pip install ruff
ruff --version
# ruff 0.17.0

uv を使っているなら、開発用の依存に入れるか、uvx で入れずに実行できます。

uv add --dev ruff
uvx ruff@0.17.0 check .     # 版を指定してその場で実行

チームで使うときは、版を固定します。 Ruff は 0.x の版でも既定のルールや書式が変わることがあり、0.16.0 と 0.17.0 はどちらも「破壊的な変更」を含むリリースでした。uv の使い方は、関連記事「uvの使い方|pip・venvからの移行とチーム開発での使い方」で扱っています。

ruff check を動かす(既定のルールのまま)

次のコードを用意しました(src/app/service.py)。よくある問題をわざと入れています。

import sys
import os, json
from typing import List, Optional
import requests


def load_users(path, cache={}):
    if path in cache:
        return cache[path]
    try:
        with open(path) as f:
            data = json.load(f)
    except:
        print(f"failed to load")
        return None
    users: List[dict] = [u for u in data if u["active"] == True]
    cache[path] = users
    return users


def find(users: List[dict], name: Optional[str] = None):
    if name == None:
        return users
    result = list([u for u in users if u["name"] == name])
    return result


def total(xs):
    l = 0
    for x in xs: l += x
    square = lambda v: v * v
    return square(l)


class Client(object):
    def __init__(self, base_url):
        self.base_url = base_url
        self.session = requests.Session()

    def get(self, path):
        return self.session.get("%s/%s" % (self.base_url, path), timeout=10).json()

設定ファイルなしで ruff check . --output-format concise(1行1件の短い表示)を実行した結果です。

src/app/service.py:1:1: I001 [*] Import block is un-sorted or un-formatted
src/app/service.py:1:8: F401 [*] `sys` imported but unused
src/app/service.py:2:8: F401 [*] `os` imported but unused
src/app/service.py:3:1: UP035 `typing.List` is deprecated, use `list` instead
src/app/service.py:7:28: B006 Do not use mutable data structures for argument defaults
src/app/service.py:13:5: E722 Do not use bare `except`
src/app/service.py:14:15: F541 [*] f-string without any placeholders
src/app/service.py:16:12: UP006 [*] Use `list` instead of `List` for type annotation
src/app/service.py:21:17: UP006 [*] Use `list` instead of `List` for type annotation
src/app/service.py:21:35: UP045 [*] Use `X | None` for type annotations
src/app/service.py:24:14: C411 Unnecessary `list()` call (remove the outer call to `list()`)
src/app/service.py:35:14: UP004 [*] Class `Client` inherits from `object`
src/app/service.py:41:33: UP031 Use format specifiers instead of percent format
Found 13 errors.
[*] 8 fixable with `--fix` (3 unsafe fixes available with `--fix --unsafe-fixes`).
表示 意味
src/app/service.py:7:28 ファイル:行:列
B006 ルールのコード。B は接頭辞(flake8-bugbear 由来)
[*] --fix で直せる(安全な修正がある)
3 unsafe fixes available 動きが変わるかもしれない修正。--unsafe-fixes を付けたときだけかかる

既定の表示(full)では、問題の箇所と、直したあとの差分が表示されます。B006 の表示の一部です。

B006 Do not use mutable data structures for argument defaults
 --> src/app/service.py:7:28
  |
7 | def load_users(path, cache={}):
  |                            ^^
help: Replace with `None`; initialize within function
   |
   - def load_users(path, cache={}):
7  + def load_users(path, cache=None):
8  +     if cache is None:
9  +         cache = {}
10 |     if path in cache:
   |
note: This is an unsafe fix and may change runtime behavior

0.16 以降の既定では出なくなったもの

同じファイルを、0.15 までの既定と同じ --select E4,E7,E9,F で調べると、結果が違いました。

コード 内容 0.17.0 の既定 --select E4,E7,E9,F
E711 == None 出ない 出る
E712 == True 出ない 出る
E741 変数名 l 出ない 出る
E701 for x in xs: l += x(1行に複数の文) 出ない 出る
E731 square = lambda v: v * v 出ない 出る
E401 import os, json 出ない(I001 に含まれる) 出る
F403・F405 from app.service import * 出ない 出る
B006・UP006・C411 など bugbear・pyupgrade など 出る 出ない

0.16.0 のリリースノートには、意見が分かれやすい E・F の 18 ルール(E401・E402・E701〜E703・E711〜E714・E721・E731・E741〜E743・F403・F405・F406・F722)を既定から外したとあります(F406 は 0.17.0 で既定に戻りました)。これらを使いたいときは extend-select で足します。0.15 までと同じ既定に戻すには select = ["E4", "E7", "E9", "F"] と書きます。

print を出す T201 や、セキュリティの S は既定では有効ではありません。 既定で有効なルールは、公式のルール一覧で ✅ が付いたものです。

pyproject.toml の設定例(完成形)

[project]
name = "app"
version = "0.1.0"
requires-python = ">=3.12"

[tool.ruff]
line-length = 100                 # 既定は 88
src = ["src", "tests"]            # 自分のパッケージの場所(import の分類に使う)
extend-exclude = ["migrations"]   # 既定の除外(.venv など)に足す

[tool.ruff.lint]
# 既定のルールに足す
extend-select = ["E711", "E712", "E741", "S", "T20"]
ignore = ["UP031"]                # % 書式はこのプロジェクトでは許す

[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = ["S101", "F403", "F405"]   # テストの assert と import * を許す

[tool.ruff.lint.isort]
known-first-party = ["app"]

[tool.ruff.format]
quote-style = "double"
docstring-code-format = true      # docstring の中のコード例も整形

この設定で ruff check . を実行すると、17件になりました。足した E711・E712・E741 と T201(print)が出て、外した UP031 は出ませんでした。tests の from app.service import * と assert は、per-file-ignores で出ませんでした。

target-version を書かないと、requires-python から決まります。 この設定で ruff check --show-settings を見ると、linter.unresolved_target_version = 3.12 でした。どちらも無いときの既定は、0.17.0 で py310 から py311 に変わりました(ruff config target-version の Default value: "py311")。

select・extend-select・ignore の違い

書き方 動き
select = ["E", "F"] 既定を捨てて、書いたものだけにする
extend-select = ["S"] 既定(または select)に足す
ignore = ["UP031"] 外す
select = ["ALL"] 全ルール。矛盾する組(D203 と D211 など)は警告 are incompatible. Ignoring … が出て片方が外れた

ドキュメントでは、指定は「ALL < カテゴリ < リンター < 接頭辞 < ルール」の順に細かいものが優先され、CLI の指定は pyproject.toml より優先されます。0.16 以降に select だけを書くと、増えた既定のルールは使われません。 既定を生かすなら extend-select を使います。

主な設定項目と既定値

ruff config <項目> で確かめた既定値です。

項目 既定 内容
line-length 88 1行の長さ(E501 とフォーマッターの両方)
target-version "py311" 対象の Python。requires-python があればそちら
extend-exclude [] 調べないファイルを足す
respect-gitignore true .gitignore のファイルは調べない
lint.mccabe.max-complexity 10 C901(複雑すぎる関数)のしきい値
lint.isort.known-first-party [] 自分のパッケージ名
lint.isort.force-single-line false from x import a, b を1行ずつにする
lint.pydocstyle.convention なし "google"・"numpy"・"pep257"
format.quote-style "double" "single"・"preserve"
format.skip-magic-trailing-comma false 末尾のカンマで改行を保つ動きを止める
format.docstring-code-format false docstring の中のコードも整形
format.line-ending "auto" "lf"・"cr-lf"・"native"

設定ファイルは pyproject.toml の [tool.ruff] のほか、ruff.toml・.ruff.toml も使えます。ruff.toml では [tool.ruff] を付けず、[lint]・[format] と書きます。

--fix と --unsafe-fixes

上の設定で ruff check . --fix を実行しました。

Found 18 errors (10 fixed, 8 remaining).
5 unsafe fixes available with `--fix --unsafe-fixes`.

安全な修正(import の整理・List → list・Optional[str] → str | None・(object) の削除・不要な f の削除)だけがかかりました。 件数が 17 から 18 になっているのは、修正のあとに調べ直して見つかった分も数えているためです。

続けて --fix --unsafe-fixes を実行すると、残りのうち5件が直り、3件が残りました。

src/app/service.py:14:5: E722 Do not use bare `except`
src/app/service.py:29:5: E741 Ambiguous variable name: `l`
src/app/service.py:30:18: E741 Ambiguous variable name: `l`
Found 8 errors (5 fixed, 3 remaining).

修正後のコードの一部です。

import json

import requests


def load_users(path, cache=None):
    if cache is None:
        cache = {}
    if path in cache:
        return cache[path]
    try:
        with open(path) as f:
            data = json.load(f)
    except:
        return None
    users: list[dict] = [u for u in data if u["active"]]
    cache[path] = users
    return users


def find(users: list[dict], name: str | None = None):
    if name is None:
        return users
    result = [u for u in users if u["name"] == name]
    return result

注意したいのは、T201 の unsafe な修正で print(...) の行が消えたことです。 ほかにも、u["active"] == True が u["active"] に変わりました(1 や "yes" も真になるので、意味が変わる場合があります)。--unsafe-fixes は差分を確かめてから使います。 差分だけ見るには ruff check . --fix --unsafe-fixes --diff が使えます。T20 だけで試すと、- print(f"failed to load") の差分と Would fix 1 error. が表示され、ファイルは書き換わりませんでした。

修正の種類 かけ方 例
安全(safe) --fix F401 未使用の import、UP006、I001
安全でない(unsafe) --fix --unsafe-fixes B006、E712、T201(print の削除)、C411
修正なし 手で直す E722 bare except、E741

ルールごとに安全・安全でないを変えるには、lint.extend-safe-fixes・lint.extend-unsafe-fixes を使います。自動で直されたくないルールは lint.unfixable に書きます(例:unfixable = ["F401"] で、書きかけのコードの import が消えるのを防ぐ)。

ruff format

ruff format --check .    # 変わるファイルがあれば終了コード 1(CI 向け)
ruff format --diff .     # 差分を表示(書き換えない)
ruff format .            # 書き換える

--fix のあとのファイルで実行した結果です。

$ ruff format --diff .
--- src/app/service.py
+++ src/app/service.py
@@ -27,7 +27,8 @@

 def total(xs):
     l = 0
-    for x in xs: l += x
+    for x in xs:
+        l += x
     square = lambda v: v * v
     return square(l)


1 file would be reformatted, 1 file already formatted

$ ruff format .
1 file reformatted, 1 file left unchanged

ruff format は、リントの問題(l という名前、lambda の代入)は直しません。 書式(改行・インデント・引用符・空白)だけを変えます。リントと書式は別のコマンドなので、両方を実行します。

0.16.0 から、ruff format は Markdown ファイルの中の Python コードブロックも整形します。 対象から外すには、extend-exclude = ["*.md"] のように除外します。

よく使うルール接頭辞の一覧

ruff linter で表示される 59 のリンターのうち、よく使うものです。「0.17 の既定」は、何も設定しないときに有効だったルールの数(ruff check --show-settings で数えたもの)です。

接頭辞 元のツール 内容 0.17 の既定
F Pyflakes 未使用の import・変数、未定義の名前 40
E / W pycodestyle 書式・文法(E501 行の長さなど) 2 / 1
I isort import の並び順 1(I001)
UP pyupgrade 新しい書き方へ(List → list など) 42
B flake8-bugbear バグになりやすい書き方(可変の既定値など) 29
C4 flake8-comprehensions 内包表記の無駄 17(C 全体)
SIM flake8-simplify もっと簡単に書ける箇所 21
RUF Ruff 独自 未使用の noqa(RUF100)など 36
PL Pylint PLE(エラー)・PLW(警告)・PLR・PLC 74
PYI flake8-pyi 型スタブ(.pyi) 47
S flake8-bandit セキュリティ(shell=True、assert など) 3
T20 flake8-print print の残り 0
N pep8-naming 名前の付け方 1
D pydocstyle docstring 1
ANN flake8-annotations 型注釈の書き忘れ 0
PT flake8-pytest-style pytest の書き方 6
PTH flake8-use-pathlib os.path より pathlib 2
DTZ flake8-datetimez タイムゾーンなしの datetime 3
C90 mccabe 関数の複雑さ(C901) 0

1つのルールの説明は ruff rule B006 で表示できます。どのリンターから来たか、修正があるか、なぜ問題かが出ます。

flake8・isort・black からの移行

出力を比べた結果

比べたもの 結果
black と ruff format --isolated サンプルのファイルで、出力が完全に一致
isort --profile black と ruff check --select I --fix --isolated 出力が完全に一致
flake8(既定)と ruff check --select E,F,W(1087 ファイル・372,374 行) 2回測って flake8 41.9 秒・57.2 秒、ruff 0.62 秒・0.87 秒(2 CPU、キャッシュなし)

flake8 と ruff の件数は、flake8 が 40,137 件、ruff が 6,651 件で、大きく違いました。flake8 の上位は E501(79 文字超え)・E231・E122 で、ruff では行の長さの既定が 88 文字で、E231 などの空白のルールは preview(--preview が必要)でした(ruff rule E225 に「This rule is in preview and is not stable」)。空白の扱いは ruff format に任せる考え方です。

設定の対応表

元の設定 Ruff の設定
flake8 max-line-length = 100 line-length = 100
flake8 extend-ignore = E203 lint.ignore = ["E203"](E203 は preview のルール)
flake8 per-file-ignores = tests/*:S101 [tool.ruff.lint.per-file-ignores] に "tests/*" = ["S101"]
flake8 max-complexity = 10 lint.mccabe.max-complexity = 10 と extend-select = ["C901"]
flake8 exclude extend-exclude(既定の除外に足す)
flake8 プラグイン(bugbear・comprehensions など) B・C4 などの接頭辞を select
isort profile = "black" 不要(既定で black 互換の並び)
isort known_first_party lint.isort.known-first-party
isort force_single_line lint.isort.force-single-line
black line-length line-length(リントと共通)
black skip-string-normalization format.quote-style = "preserve"
black skip-magic-trailing-comma format.skip-magic-trailing-comma

isort の機能は ruff check の I ルールで、ruff format ではありません。 import の並び替えもしたいときは、ruff check --select I --fix を実行してから ruff format を実行します(I を select に入れていれば ruff check --fix で並ぶ)。

noqa と ruff: ignore(無視のしかた)

書き方 範囲
x = 1 # noqa: F841 その行の F841 だけ
# noqa(コードなし) その行のすべて
# ruff: noqa ファイル全体のすべて
# ruff: noqa: F401 ファイル全体の F401
# ruff: ignore[S602](0.16 から) 次の論理行(その文全体)
# ruff: file-ignore[F401, ARG001] ファイル全体

# ruff: ignore[S602] を def の上に置くと、関数の中の subprocess.run(..., shell=True) は無視されず、RUF100 Unused suppression (unused: S602) が出ました。 ドキュメントのとおり、対象は直後の論理行(ここでは def の行)です。文の直前に置くと無視されました。

def run(cmd):
    # ruff: ignore[S602]
    return subprocess.run(cmd, shell=True, check=True)

使われていない noqa は RUF100 で見つかります(0.17 の既定で有効)。 有効でないルールのコードを書いた # noqa: E501 も Unused noqa directive (non-enabled: E501) になりました。

既存のコードに Ruff を入れるとき、いまある違反に一括で無視のコメントを付けるには --add-noqa(# noqa: …)か --add-ignore(# ruff: ignore[…])を使います。どちらも、5件の違反に対して Added 5 noqa directives.・Added 5 ignore comments. と表示されました。

pre-commit と CI

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.17.0
    hooks:
      - id: ruff-check
        args: [--fix]
      - id: ruff-format

ruff-pre-commit の README では、--fix を付けるときは、ruff-check を ruff-format より前に置くとされています。pre-commit の設定は、関連記事「pre-commit設定|.pre-commit-config.yamlとフック一覧」で扱っています。

GitHub Actions では、版を固定して --output-format github を付けると、プルリクエストの該当行に注釈が付きます。

# .github/workflows/lint.yml
name: lint
on: [push, pull_request]
jobs:
  ruff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install ruff==0.17.0
      - run: ruff check . --output-format github
      - run: ruff format --check .

--output-format github の出力は次の形でした。

::error title=ruff (E722),file=…/src/app/service.py,line=14,col=5,endLine=14,endColumn=11::src/app/service.py:14:5: E722 Do not use bare `except`

出力の形式は concise・full・json・junit・github・gitlab・sarif などから選べます。

終了コード 意味(ドキュメント)
0 違反なし、またはすべて自動で直った
1 違反がある
2 設定や CLI の誤りなどで異常終了

よくある質問

Q. Ruff を入れたら、型の誤りも見つかりますか。 A. Ruff は型チェッカーではないので、引数の型の不一致などは見つけません。型チェックには mypy や pyright を組み合わせます。関連記事「mypyとpyrightの違いと使い方|設定例・CI導入・出力比較」で扱っています。

Q. 既定のルールが多すぎて、既存のコードで大量に出ます。 A. 0.15 までの既定に戻すなら select = ["E4", "E7", "E9", "F"] です。増えたルールを少しずつ入れるなら、ruff check --statistics で多いルールを確かめ、まず ignore に入れて、直せたものから外していく方法があります。

Q. VS Code で保存時に整形したいです。 A. Ruff の VS Code 拡張を入れ、Python の既定のフォーマッターを Ruff にします。保存時の import 整理と自動修正は、source.organizeImports.ruff・source.fixAll.ruff のコードアクションで行えます。

Q. 型ヒントの新しい書き方(list[int]・X | None)はいつから使えますか。 A. UP006・UP045 は、target-version で使える書き方に直します。書き方の違いは関連記事「Pythonの型ヒントの書き方|3.12以降の新しい書き方まで」で扱っています。

まとめ

  • ruff check がリント、ruff format が書式。isort の役割は ruff check の I
  • 0.16 から既定のルールは 400 超。B・UP・RUF などが入り、== None(E711)や l(E741)などは既定から外れた
  • 設定は pyproject.toml の [tool.ruff]。既定を生かすなら select ではなく extend-select
  • --unsafe-fixes は print の削除や == True の書き換えもするので、--diff で確かめてから
  • black・isort(profile black)と出力が一致。flake8 より大幅に速かった(約 42〜57 秒 → 1 秒未満)
  • 版を固定して、pre-commit と CI(--output-format github・format --check)で回す

参考・出典

確認日はいずれも 2026年10月13日です。

  • Ruff ドキュメント「Configuring Ruff」:https://docs.astral.sh/ruff/configuration/
  • Ruff ドキュメント「The Ruff Linter」(ルールの選び方・修正の安全性・無視のしかた・終了コード):https://docs.astral.sh/ruff/linter/
  • Ruff ドキュメント「Rules」:https://docs.astral.sh/ruff/rules/
  • Ruff ドキュメント「Integrations」:https://docs.astral.sh/ruff/integrations/
  • Astral ブログ「Ruff v0.16.0」:https://astral.sh/blog/ruff-v0.16.0
  • Ruff リリース 0.17.0:https://github.com/astral-sh/ruff/releases/tag/0.17.0
  • astral-sh/ruff-pre-commit:https://github.com/astral-sh/ruff-pre-commit

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Ruffの使い方|pyproject.toml設定とルール一覧」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?