目次
7 章

モジュール、パッケージと pyproject.toml

import システム、モジュールとパッケージの違い、__init__.py と __main__、そして pyproject.toml で依存関係・ツール設定・配布まで一箇所に整理します。

1部の最終章です。六つの章の間、一つのファイルの中でコードを書いてきました。本章は 複数のファイルに分け、それらをプロジェクトとしてまとめる方法import システム、モジュールとパッケージ、そしてそのすべてを定義する pyproject.toml です。

本章の pyproject.toml は本書全体で継続して拡張されていきます。第30章 型チェッカ設定と CI 統合[tool.ruff] / [tool.pyright] セクションを正式設定に育て、第32章 uv でライブラリ配布[build-system] / [project.scripts] セクションを使って最初のライブラリを PyPI に出します。本章はそのすべての作業の土台を敷きます。

モジュール — ファイル一つがモジュール #

Python では .py ファイル一つがそのままモジュール です。他の場所から import して使います。

math_utils.py
def add(a: int, b: int) -> int:
    return a + b

def multiply(a: int, b: int) -> int:
    return a * b

PI = 3.141592
main.py
import math_utils

print(math_utils.add(2, 3))     # 5
print(math_utils.PI)            # 3.141592

四つの import 形 #

import の変形
# 1. モジュールまるごと
import math_utils
math_utils.add(1, 2)

# 2. 別名
import math_utils as mu
mu.add(1, 2)

# 3. 特定の名前だけ取り込む
from math_utils import add, PI
add(1, 2)

# 4. 別名 + 取り込み
from math_utils import add as plus
plus(1, 2)

from m import * はほぼ使わない #

🚫 通常は避ける
from math_utils import *

名前衝突、どこから来た名前か追跡不可、静的解析器の混乱。新規コードではほとんど使いません。例外はインタラクティブシェルで一時的に試す場合くらいです。

パッケージ — ディレクトリがパッケージ #

複数のモジュールをまとめたいときは ディレクトリ にします。

パッケージ構造
my_app/
├── __init__.py
├── auth.py
├── db.py
└── handlers/
    ├── __init__.py
    ├── user.py
    └── post.py

この構造で次のようになります。

パッケージの import
from my_app import auth
from my_app.db import connect
from my_app.handlers.user import create_user

__init__.py — 二つの役割 #

__init__.py は二つの仕事をします。

  1. このディレクトリがパッケージであることを示す (歴史的役割 — Python 3.3 以降はなくても namespace package として扱われる)
  2. パッケージが import されるときに実行されるコード — 公開 API を整理したり初期化したりする
my_app/__init__.py
"""my_app — 例題アプリケーション。"""

from my_app.auth import login, logout
from my_app.db import connect

__all__ = ["login", "logout", "connect"]
__version__ = "0.1.0"

こうすれば呼び出し側は次のようになります。

短くなった import
from my_app import login, connect

内部構造(auth モジュール、db モジュール) が露出しません。公開 API を一箇所で定義する パターンです。

__all__from my_app import * の際に取り込まれる名前のホワイトリストですが、実際には 公開 API を明示する文書 の役割としてより頻繁に使われます。

namespace package — __init__.py がない場合 #

3.3+ からは __init__.py のないディレクトリもパッケージになります(implicit namespace package) 。短所は次の通りです。

  • 初期化コードを置く場所がない
  • 一部ツールが認識しない場合がある (古いバージョンたち)

新しいパッケージを作るときは __init__.py を置く 方が安全です。空でも構いません (pass の一行も不要、空のファイル) 。

絶対 import vs 相対 import #

同じパッケージの中で別のモジュールを取り込むとき、二つの方法があります。

my_app/handlers/user.py
# 絶対 import
from my_app.db import connect
from my_app.auth import current_user

# 相対 import
from ..db import connect
from ..auth import current_user

.. は「一段上」、. は「同じディレクトリ」です。

両方とも使えますが絶対 import を推奨 します (PEP 8) 。どこから取り込んでいるか一目で分かり、ファイルを移動したときに壊れる可能性も小さくなります。相対 import はパッケージ内部の短い距離でだけ使うくらいが無難です。

__main__ — モジュールを直接実行するとき #

スクリプトとして実行したときだけ動くコードを置く場所です。

cli.py
def main():
    print("hello!")

if __name__ == "__main__":
    main()

このファイルは二つの方法で使えます。

直接実行
$ uv run cli.py
hello!         ← __name__ が '__main__'
別の場所から import
import cli
cli.main()    # 明示的に呼び出せば動く
# import 時点では main() は呼ばれない

if __name__ == "__main__": がないと import しただけで main が実行され、副作用が発生します。常にこのガードを置く のが模範です。

パッケージ単位の実行 — __main__.py #

パッケージ自体を実行可能にしたいなら次の通りです。

実行可能なパッケージ
my_cli/
├── __init__.py
├── __main__.py     ← ここに main エントリポイント
└── core.py
my_cli/__main__.py
from my_cli.core import run

if __name__ == "__main__":
    run()

こうすれば次のように実行できます。

パッケージとして実行
$ uv run python -m my_cli

CLI 道具を作るときによく使うパターンです。第33章 CLI 道具作り (Typer) で詳しく扱います。

標準ライブラリ — 同じ import システム #

標準ライブラリも同じように import します。別途インストールなしですぐ使えます。

よく使う標準モジュール
import os                # ファイルシステム
import sys               # インタプリタ、argv
import json              # JSON
import re                # 正規表現
import datetime          # 日付/時刻
import pathlib           # パスオブジェクト
from collections import Counter, defaultdict
from itertools import chain, groupby
from functools import lru_cache, partial

標準ライブラリは 公式ドキュメント がもっとも速い参考になります。

pyproject.toml — プロジェクトの単一の定義 #

第1章uv init で作ったあのファイルです。本章で詳しく見ていきます。

pyproject.toml
[project]
name = "my-app"
version = "0.1.0"
description = "例題アプリケーション"
readme = "README.md"
requires-python = ">=3.14"
authors = [
    { name = "Curtis", email = "you@example.com" },
]
license = { text = "MIT" }
dependencies = [
    "httpx>=0.28",
    "pydantic>=2.10",
]

[project.optional-dependencies]
docs = ["mkdocs>=1.6"]

[dependency-groups]
dev = [
    "pytest>=8.0",
    "ruff>=0.7",
    "pyright>=1.1",
]

[project.scripts]
my-app = "my_app.cli:main"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

主要なセクションを一つずつ見ていきます。

[project] — PEP 621 メタデータ #

名前、バージョン、説明、Python バージョン要求、作成者、ライセンス、ランタイム依存関係

[project.optional-dependencies] — オプション依存関係 #

pip install 'my-app[docs]' のような形でユーザーが選んでインストールできるまとまりです。ライブラリを配布するときによく使います。

[dependency-groups] — 開発/テスト用 #

PEP 735 で標準化されたセクションです。配布には含まれないが開発時には必要な ツールをまとめます。uv が uv add --dev で自動的に追加します。

[project.scripts] — CLI エントリポイント #

このセクションがあると、パッケージインストール時に コンソールコマンドが自動的に作られます。

[project.scripts]
my-app = "my_app.cli:main"

インストール後は次の通りです。

$ my-app
# my_app.cli モジュールの main 関数が実行される

CLI 道具を作るなら本セクションが核心です。第33章 CLI 道具作り (Typer) で実際の CLI を作り、本セクションでエントリポイントをまとめます。

[build-system] — ビルドバックエンド #

PyPI に上げるパッケージなら必要です。hatchlingsetuptoolspdm-backend など複数の選択肢がありますが、新規プロジェクトは hatchling が軽量で無難です。

第32章 uv でライブラリ配布 で本セクションを正式に書き、PyPI へのリリース手順までつなげます。

pyproject.toml の中にツール設定も #

リンター / フォーマッタ / 型チェッカも同じファイルに設定を置きます。

ツール設定
[tool.ruff]
line-length = 100
target-version = "py314"

[tool.ruff.lint]
select = ["E", "F", "I", "N", "UP", "B"]

[tool.pyright]
typeCheckingMode = "strict"
pythonVersion = "3.14"

[tool.pytest.ini_options]
testpaths = ["tests"]

setup.cfg.flake8mypy.inipytest.ini のような散らばっていたファイルが 一つにまとまります。 本章ではプレビュー程度だけ見て、正式設定は第30章 型チェッカ設定と CI 統合 で扱います。

uv の日常コマンド — もう一度 #

第1章 で見たものに加えて、よく使う流れを示します。

日常の流れ
# 新しいプロジェクト
uv init my-app --python 3.14

# 依存関係
uv add httpx pydantic
uv add --dev pytest ruff pyright
uv remove old-package

# 同期 (別マシンで / 新たに受け取ったとき)
uv sync

# 実行
uv run python main.py
uv run pytest
uv run ruff check
uv run pyright

# 一時実行 (プロジェクトの依存関係ではないツール)
uvx ruff check .          # 一度だけ回して環境を汚さない

# Python 自体のアップグレード
uv python install 3.14

uvx隔離された環境で一度だけ実行 するコマンドです。pipx と同じ役割で、グローバルに道具をインストールしないので安全です。

1部のまとめ — ここまでで揃った道具 #

7 章を経てモダンPython の 基礎インフラ が揃いました。少なくとも次のようなコードは直接書き、読めるようになっています。

ここまでで揃った道具で書ける一行まとめ
from collections.abc import Callable

type UserId = int

def find_users(
    ids: list[UserId],
    *,
    transform: Callable[[UserId], str] = str,
    skip_invalid: bool = True,
) -> dict[UserId, str]:
    """ID リストから変換されたユーザーマップを作る。"""
    result: dict[UserId, str] = {}
    for uid in ids:
        try:
            result[uid] = transform(uid)
        except ValueError:
            if not skip_invalid:
                raise
    return result

型ヒント、| union、組み込みジェネリクス、keyword-only、Callable、例外処理、docstring — すべて 1 部で扱った道具です。

練習問題 #

  1. 新しいプロジェクト mathkit を作り、パッケージ構造 mathkit/{__init__.py, basic.py, advanced.py, __main__.py} を手で作ってください。basic.pyaddsubtractadvanced.pyfactorial を置き、__init__.py で公開 API として露出します。uv run python -m mathkit でパッケージ単位の実行が動作するか確認します。
  2. 上のプロジェクトの pyproject.toml[project.scripts]mathkit = "mathkit.__main__:main" を追加してください。uv pip install -e . または uv tool install --from . mathkit でインストール後、シェルから mathkit コマンドが動作するか確認します。
  3. 同じプロジェクトに [dependency-groups] の dev グループで pytestruffpyright を追加した後、tests/test_basic.pyadd(2, 3) == 5 を検証する簡単なテストを書き、uv run pytest が通るか確認します。

一行まとめ: ファイル = モジュール、ディレクトリ = パッケージ。import は絶対パスを推奨。if __name__ == "__main__": ガードは常に、パッケージ単位の実行は __main__.pypyproject.toml 一枚にメタデータ / 依存関係 / スクリプト / ツール設定がすべて集まる。uv の日常は init / add / sync / run / uvx

次の章 #

1 部が終わり 2 部 コードの構造化 が始まります。最初の章は 第8章 dataclass と __slots__ — データのまとまりクラスを短く安全に作る道具です。1 部の関数 / コレクションの道具では十分に解けなかった「形が決まったオブジェクト」の問題が dataclass で綺麗に解けます。

X