AIにPythonを理解したコードを書かせる試行錯誤
- Event:
AIを“相棒”にするための勉強会
- Presented:
2025/10/29 nikkie
お前、誰よ
nikkie(にっきー)・Python使い
@ftnext 私が欲しい小さなライブラリをおすそ分けでOSS(llm-claude-code)
機械学習エンジニア。 Speeda AI Agent 開発(We're hiring!) [1]
宣伝:書籍『サム・アルトマン』、いかがですか?
今回は Claude Code の話
Codex CLI にゴリゴリOSSのソース読ませもしています(DeepWikiの代わりに)
言語を少し深く知ってる開発者なら、いまのAIよりは自分のほうが(速くはないけど)詳しいって感覚なんですかねぇ #kichijojipm
— nikkie(にっきー) / にっP (@ftnext) September 6, 2025
私にとってはPythonがそうで、Claude Codeが書いてくるコード、Pythonを理解してないので、リンタが怒るようなコードをいつも書くんですよね(リンタを設定して渡さなきゃ
あなた Python 分かってます?
Claudeは人類に比べて速く、安くPythonを書く
ただ私からはPythonを 理解しているとは言えない
Pythonを理解しているなら書かないコードを頻繁に書くため
例:f-string
フォーマット済み文字列リテラル
式 を含められる
>>> name = "りせ"
>>> f"こんにちは、{name}さん!"
'こんにちは、りせさん!'
>>> print(f"2 + 3 = {2 + 3}")
2 + 3 = 5
ロギングにf-stringは使わない [2]
logger.info("%s - Something happened", user)
logger.error("Python version: %s", sys.version)
拙ブログ Pythonのログメッセージにf-stringはいけません。そこのClaude、私はあなたに言っているんですよ
しかしClaudeは平気でf-stringを使う
logger.info(f"{user} - Something happened")
logger.error(f"Python version: {sys.version}")
f-stringはその場で評価されるためです。%-formatならログレベルが有効なときのみ評価されます
💡リンタで指摘しよう
-
G004Logging statements should not usef"..."for their first argument
Ruff
Rustで書かれたPythonのリンタ兼フォーマッタ。速い [4]
flake8やpylint(リンタ)・black(フォーマッタ)からRuffへの置き換えが進む
$ uvx ruff check --fix --extend-select I && uvx ruff format
拙ブログ Ruffは format と check --fix の2つのコマンドでフォーマットする (Ruff 0.7.2)
hatch fmt
Ruffのリントとフォーマットは現状別々のコマンド
普通にRuffを流すよりも 厳しいルール
Claude Codeの フック を設定
Pythonを書いたときに
hatch fmtを実行するhatch fmtの出力を繰り返しClaudeに見せる(逃 し ま せ ん)
ローカルのプロジェクトに設定
.claude/
├── hooks/
│ └── python_format.sh
└── settings.local.json
PostToolUseフック
EditまたはWriteでPythonファイルを書いたら
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | { read file_path; if echo \"$file_path\" | grep -q '\\.py$'; then \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/python_format.sh \"$file_path\"; fi; }"
}
]
}
]
}
}
python_format.sh
#!/usr/bin/env bash
set -u
hatch fmt "$1" 1>&2
exit_code=$?
if [ $exit_code -eq 1 ]; then
exit 2
else
exit $exit_code
fi
フックのエラーメッセージをClaudeに見せるために
python_format.shの終了コードを 2 にする(Claude Codeをブロック)hatch fmtの出力を stderr へ(Claudeが見る)
拙ブログ フックでリンタ(hatch fmt)のエラーを Claude Code に見せて、Python を理解している実装をさせる
デモ
エラトステネスの篩をPythonで書いて
⎿ PostToolUse:Write hook returned blocking error: [jq -r
'.tool_input.file_path' | { read file_path; if echo "$file_path"
| grep -q '\.py$'; then
"$CLAUDE_PROJECT_DIR"/.claude/hooks/python_format.sh
"$file_path"; fi; }]: cmd [1] | ruff check --config
'/.../hatch/env/.internal/
hatch-static-analysis/.config/ukmfGEkg/ruff_defaults.toml' --fix
/.../claude-code-harness
/sieve_of_eratosthenes.py
sieve_of_eratosthenes.py:11:12: PLR2004 Magic value used in
comparison, consider replacing `2` with a constant variable
sieve_of_eratosthenes.py:27:12: RET504 Unnecessary assignment to
`primes` before `return` statement
sieve_of_eratosthenes.py:35:5: T201 `print` found
sieve_of_eratosthenes.py:36:5: T201 `print` found
sieve_of_eratosthenes.py:37:5: T201 `print` found
Found 5 errors.
No fixes available (4 hidden fixes can be enabled with the
`--unsafe-fixes` option).
⏺ Pythonのlintエラーが 08:24 PM claude-sonnet-4-5-20250929
検出されました。修正
します。
sieve_of_eratosthenes.pyMINIMUM_PRIME = 2
def sieve_of_eratosthenes(n):
"""
エラトステネスの篩を使って、n以下の素数を全て求める
Args:
n: 上限値
Returns:
n以下の素数のリスト
"""
if n < MINIMUM_PRIME:
return []
# 最初は全ての数を素数候補とする
is_prime = [True] * (n + 1)
is_prime[0] = is_prime[1] = False # 0と1は素数ではない
# 2からsqrt(n)まで処理
for i in range(MINIMUM_PRIME, int(n**0.5) + 1):
if is_prime[i]:
# iの倍数を全て合成数としてマーク
for j in range(i * i, n + 1, i):
is_prime[j] = False
# 素数のリストを作成して返す
return [i for i in range(n + 1) if is_prime[i]]
# 使用例
def main():
# 100以下の素数を求める
n = 100
primes = sieve_of_eratosthenes(n)
print(f"{n}以下の素数:") # noqa: T201
print(primes) # noqa: T201
print(f"\n素数の個数: {len(primes)}") # noqa: T201
if __name__ == "__main__":
main()
時間があったら話すコンテンツ
Pythonでフックを書く
プラグインで配布
1️⃣ cchooks
Pythonスクリプトでフックを書ける [7]
拙ブログ Claude Code のフックを Python スクリプトで書ける cchooks で hatch fmt を実行するフックを書く
inline script metadataと奇跡的相性🫶
#!/usr/bin/env -S uv run
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "cchooks",
# ]
# ///
import subprocess
import cchooks
c = cchooks.create_context()
if c.tool_input.get("file_path", "").endswith(".py"):
file_path = c.tool_input["file_path"]
result = subprocess.run(["uvx", "hatch", "fmt", file_path], capture_output=True, text=True)
if result.returncode == 1: # Need to fix formatting
cchooks.exit_block(f"Fix `hatch fmt` issues:\n{result.stdout}")
else:
cchooks.exit_success()
2️⃣ プラグイン
インストールできるが、PostToolUseの終了コード2で 止まって しまう(v2.0.27で観測)
まとめ🌯 AIにPythonを理解したコードを書かせる試行錯誤
Claude Code、Pythonを理解してないよなあ
hatch fmt をPostToolUseフックに設定して逃さない
引き続き磨き込んでいくぞ(コメント多い、型チェック)
ご清聴ありがとうございました
Happy AI-assisted development🤖
宣伝:10/30(木)31(金)AI駆動開発カンファレンス オンライン無料です!!
学マスの秦谷美鈴さんが言ったとされる
— nikkie(にっきー) / にっP (@ftnext) 2025年7月26日
「わたしが上で、あなたが下です」https://t.co/tMEWieYG3i
ChatGPTとかClaudeを使うときのマインドセットに通じるものがあるかもしれません。LLMが下