- Python
Ruffの使い方|pyproject.toml設定とルール一覧
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期連続の黒字決算。