はじめに
── 新規プロジェクトは「src レイアウト」だけ覚えておけば勝てる。
Python でプロジェクトを始めるとき、
「どこに何を置けばいいの?」「tests は分ける?」「pyproject.toml は?」
……と迷子になりがちです。
でも、安心してください。
実は “王道レイアウト” を覚えておけば 90% 迷いは消えます。
この記事ではそのベストプラクティスを、理由付きでスッキリまとめていきます。
結論:まずは「src レイアウト」を採用せよ
現代の Python プロジェクトは srcレイアウト一択 と言っても過言ではありません。
my_project/
├─ pyproject.toml
├─ README.md
├─ LICENSE
├─ src/
│ └─ my_project/
│ ├─ __init__.py
│ ├─ core/
│ ├─ infra/
│ └─ cli.py
└─ tests/
この形にするだけで:
- “ローカルパスで import が動く” 事故が減る
-
pip install -e .で開発が超スムーズ - テストが本番コードを本当に import できているか確認しやすい
というメリットがドカッと手に入ります。
pyproject.toml を中心に管理する
昔のように setup.py を書く必要はありません。
現代の Python は pyproject.toml が主役。
最小構成は次の通り:
[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["requests"]
[project.optional-dependencies]
dev = ["pytest", "mypy", "ruff"]
[project.scripts]
my-cli = "my_project.cli:main"
ポイント:
-
nameとsrc/my_project/のフォルダ名を揃える - CLI を作るなら
[project.scripts]に書くだけでコマンド化 - dev 依存は optional-dependencies で管理するとキレイ
Poetry や Hatch を使う場合も考え方は同じです。
ディレクトリ構成の考え方(役割で分ける)
Python は「自由にファイル置いてOK」なので、逆にカオスになりがち。
そこで 役割ごとパッケージ分割 がおすすめ。
src/my_project/
├─ core/ # ドメインロジック
├─ infra/ # DB/API/外部I/O
├─ cli.py # CLI entry point
└─ __init__.py
役割分割の例
| ディレクトリ | 中身 |
|---|---|
core/ |
業務ロジック、サービスクラス |
infra/ |
DB、外部API、ファイルI/O |
cli.py |
実行エントリポイント |
__init__.py |
Public API の整理 |
「core」と「infra」みたいにレイヤー分けすると、
Clean Architecture 的にも美しい構造になります。
テストは必ずプロジェクト直下の tests/ に集める
tests/
├─ __init__.py # 空でOK(pytest的にはなくてもOK)
├─ test_core_service.py
├─ test_models.py
└─ cli/
└─ test_cli.py
なぜ分けるの?
- 本番ロジックと混ざらず見通しが良い
- pytest が自動でテストを発見
-
srcに混ぜると import バグの温床になる
pytest の設定は pyproject.toml に書くとシンプル:
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra"
設定値は config.py、環境差分は環境変数へ
Python アプリでよくある問題:
- 本番用 API URL がコードに埋め込まれている
-
.envを読み込んでない - 設定が散乱する
これを防ぐには、Pydantic BaseSettings を使うのが最強です。
# src/my_project/config.py
from pydantic import BaseSettings
class Settings(BaseSettings):
api_url: str = "https://api.example.com"
debug: bool = False
class Config:
env_file = ".env"
settings = Settings()
型 / Lint / Formatter を最初から入れておく
後から入れると地獄を見るので “最初に” 入れるのが鉄則。
- 型:
mypyorpyright - Lint:
ruff - Formatter:
ruff formatorblack
設定も pyproject.toml にまとめるのがベスト。
[tool.mypy]
strict = true
packages = ["my_project"]
[tool.ruff]
line-length = 100
target-version = "py311"
よくあるアンチパターン
反面教師としてよくある地雷もまとめておきます。
| アンチパターン | なぜ悪い? |
|---|---|
main.py, utils.py, helper.py が直下に散乱 |
どこが何をするファイルか不明瞭 |
| テストを src 内に置く | import が壊れる・汚れる |
プロジェクト直下に大量の .py
|
大規模化した瞬間に破綻 |
| venv をリポジトリにコミット | 重いし無意味。.gitignoreへ |
特に utils.py 依存のプロジェクトはだいたい後で泣きます。
まとめ
my_project/
├─ pyproject.toml
├─ README.md
├─ src/
│ └─ my_project/
│ ├─ core/
│ ├─ infra/
│ ├─ cli.py
│ └─ __init__.py
└─ tests/
-
srcレイアウト - テストは
tests/ - 設定は Pydantic BaseSettings
- 型・Lint・Formatter を早めに導入
- すべて
pyproject.tomlに集約
Python プロジェクトはこの形にしておけば、
後から拡張する時も破綻しません。