9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

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!

じゃあ要らないの?

実用上は必要です。理由:

  1. APIを整理して公開できない
  2. パッケージ初期化処理ができない
  3. IDE補完が効きにくい
  4. 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 は卒業しましょう!


📚 参考

9
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?