はじめに
Pythonのパッケージを作るとき、必ず悩むのが __init__.py の中身。
- 空でいいの?
- 何か書くべき?
- そもそも必要?
この記事で、実際にコードを動かしながら、__init__.py の役割と書くべき内容を完全解説するよ。
結論(先に言う)
# mypackage/__init__.py
# 1. バージョン情報
__version__ = "1.0.0"
# 2. 公開APIを定義
__all__ = ["add", "multiply", "Calculator", "User"]
# 3. 便利にインポートできるようにエクスポート
from .utils import add, multiply, Calculator
from .models import User
これが「ほとんどのケースで正解」なパターンです。理由を説明していきます。
🔰 そもそも __init__.py は必要?
Python 3.3以降は「なくても動く」
Python 3.3で導入された暗黙の名前空間パッケージにより、__init__.py がなくてもパッケージとして認識されます。
mypackage/ # __init__.py なし
├── utils.py
└── models.py
# これは動く
from mypackage import utils
print(utils.add(2, 3)) # => 5
# でもこれは動かない!
from mypackage import add # ImportError!
じゃあ要らないの?
実用上は必要です。理由:
- APIを整理して公開できない
- パッケージ初期化処理ができない
- IDE補完が効きにくい
from xxx import *が使えない
📦 __init__.py の4つの役割
役割1: 公開APIの定義
パッケージのディレクトリ構造:
mypackage/
├── __init__.py
├── utils.py # add(), multiply(), Calculator
└── models.py # User, Product
utils.py
def add(a: int, b: int) -> int:
return a + b
def multiply(a: int, b: int) -> int:
return a * b
class Calculator:
def __init__(self, initial_value: int = 0):
self.value = initial_value
def add(self, x: int) -> "Calculator":
self.value += x
return self
def result(self) -> int:
return self.value
__init__.py で公開する
# mypackage/__init__.py
from .utils import add, multiply, Calculator
from .models import User, Product
__all__ = ["add", "multiply", "Calculator", "User", "Product"]
使う側はこうなる
# 長いインポート(__init__.py なしだとこうなる)
from mypackage.utils import add
from mypackage.models import User
# 短いインポート(__init__.py があればこれでOK)
from mypackage import add, User
ユーザーに内部構造を意識させないのが __init__.py の役目です。
役割2: __all__ でワイルドカードインポート制御
__all__ = ["add", "multiply", "Calculator", "User", "Product"]
これを定義すると:
from mypackage import *
# __all__ に含まれるものだけがインポートされる
print(add(1, 2)) # OK
print(Calculator) # OK
print(utils) # NameError(__all__に含まれていない)
役割3: パッケージメタ情報
__version__ = "1.0.0"
__author__ = "Your Name"
__license__ = "MIT"
import mypackage
print(mypackage.__version__) # => 1.0.0
役割4: 初期化処理
# __init__.py
print(f"mypackage v{__version__} loaded")
# ログ設定、設定ファイル読み込みなど
import logging
logging.getLogger(__name__).addHandler(logging.NullHandler())
🚀 応用:遅延インポートパターン
重いモジュール(numpy, pandas, tensorflowなど)を含むパッケージで、使わない機能の初期化時間を節約したい場合。
Python 3.7+の __getattr__ を使う
# mypackage_lazy/__init__.py
import importlib
__version__ = "1.0.0"
def __getattr__(name):
"""属性アクセス時に初めてインポート"""
if name == "heavy_module":
print(f"[DEBUG] {name} を遅延インポート中...")
module = importlib.import_module(f".{name}", __name__)
globals()[name] = module # キャッシュ
return module
raise AttributeError(f"module 'mypackage_lazy' has no attribute '{name}'")
動作確認
import time
start = time.time()
import mypackage_lazy
print(f"パッケージインポート: {time.time() - start:.2f}秒")
# => 0.02秒(重いモジュールは読み込まれない)
start = time.time()
result = mypackage_lazy.heavy_module.process_data([1, 2, 3])
print(f"遅延インポート: {time.time() - start:.2f}秒")
# => 2.00秒(このタイミングで初めて読み込み)
📋 パターン別テンプレート
パターン1: シンプルなライブラリ
# __init__.py
"""mypackage - 便利な計算ライブラリ"""
__version__ = "1.0.0"
__all__ = ["add", "subtract", "multiply", "divide"]
from .operations import add, subtract, multiply, divide
パターン2: クラス中心のライブラリ
# __init__.py
"""mypackage - データモデル集"""
__version__ = "2.0.0"
__all__ = ["User", "Product", "Order", "create_user"]
from .models import User, Product, Order
from .factory import create_user
パターン3: 大規模パッケージ(遅延インポート)
# __init__.py
"""mypackage - 機械学習フレームワーク"""
import importlib
from typing import TYPE_CHECKING
__version__ = "3.0.0"
# 型チェック時のみインポート(IDE補完用)
if TYPE_CHECKING:
from . import data
from . import models
from . import training
_SUBMODULES = ["data", "models", "training"]
def __getattr__(name: str):
if name in _SUBMODULES:
return importlib.import_module(f".{name}", __name__)
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
def __dir__():
return __all__ + _SUBMODULES
❌ よくあるアンチパターン
1. 全部インポートしちゃう
# ダメな例
from .module1 import *
from .module2 import *
from .module3 import *
- 名前の衝突リスク
- 初期化が遅くなる
- 何が公開されてるか不明
2. 循環インポートを発生させる
# models.py
from .services import UserService # services.py が models をインポートしてたら死
# __init__.py でうまく順序制御するか、遅延インポートで回避
3. 副作用のある処理を書く
# ダメな例
import requests
response = requests.get("https://api.example.com/init") # インポートしただけで通信!?
🎯 まとめ
| 書くもの | 目的 | 必須度 |
|---|---|---|
__version__ |
バージョン管理 | ★★★ |
__all__ |
ワイルドカードインポート制御 | ★★☆ |
from .xxx import yyy |
公開API定義 | ★★★ |
__author__, __license__
|
メタ情報 | ★☆☆ |
__getattr__ |
遅延インポート | ★☆☆(大規模向け) |
最終回答
# __init__.py テンプレート
"""パッケージの説明をここに書く"""
__version__ = "1.0.0"
__all__ = ["公開したい", "名前を", "列挙"]
from .module1 import 公開したい
from .module2 import 名前を, 列挙
空の __init__.py は卒業しましょう!