Pythonでパッケージを作成・管理する際,必ず目にするファイルが__init__.py
である.みなさんは__init__.py
って何かわかりますか?FlaskとかDjangoとかで何となく出てくるので脳死で使っている人も多いのではないでしょうか?
私もあまりわからず使っていました!
__init__.py
を知る前に...まずはPythonのimport
処理とsys.path
によるモジュール検索の仕組み知る必要があります!そこから説明します!
1. importとsys.pathの仕組み
1.1 Pythonインタプリタとは
- ソースコードを読み込み,字句解析→構文解析→バイトコード生成→実行までを担うプログラム
-
import
文を遇うとモジュール検索と読み込みを開始する
モジュールとは?
- Pythonの“モジュール”は,関数・クラス・変数などをまとめたコードの部品
- 原則として1ファイル(
.py
)が1モジュール - モジュールは名前空間 (
namespace
) を提供し,衝突を防ぐ - 別ファイルやREPLから
import モジュール名
で再利用可能
REPLとは,Read-Eval-Print Loopの略で,Pythonの対話型実行環境のこと
1.2 sys.pathの役割
sys.path
はPythonインタプリタがモジュールやパッケージを探すパスのリストである.
主な内容:
- 標準ライブラリのインストールディレクトリ
- 実行中スクリプトの所在ディレクトリ
-
PYTHONPATH
環境変数で追加されたディレクトリ
PYTHONPATHとは?
環境変数の一つで,Pythonがモジュールを検索する際に参照するパスのリスト.
PYTHONPATH
に指定されたディレクトリは,sys.path
の先頭に追加される.
インタプリタはsys.path
を先頭から順に調べ,対象の名前に対応する
-
<name>.py
モジュール -
<name>
ディレクトリ(中に__init__.py
がある場合)
を探す.
文章で説明してもわかりにくいと思うのであるので,実際にsys.path
を確認してみましょう.
以下のディレクトリ構造のPythonプロジェクトを考えます.
my_project/
├── main.py
├── my_package/
│ ├── __init__.py
│ ├── sub_a.py
│ └── sub_b.py
└── my_module.py
sys.pathを確認するために,以下のようなコードを書いてみましょう.
import sys, pprint
import my_package # ここで実際にインポート
print('--- sys.path ---')
pprint.pprint(sys.path)
print('--- loaded modules ---')
pprint.pprint([m for m in sys.modules if m.startswith('my_package')])
このコードをmain.py
に書いて実行すると,以下のような出力が得られる.
python3 main.py
--- sys.path ---
['/Users/.../my_project', ...]
--- loaded modules ---
['my_package', 'my_package.sub_a', 'my_package.sub_b']
つまり,sys.path は
- “モジュールそのもの” ではなく
- “モジュールを探しに行くディレクトリのリスト”
を保持しているだけ
Python はこのリストに載っているディレクトリの中を順番に探し,
- 指定されたモジュール名.py
- 指定されたモジュール名という名前の パッケージ(=その名前のディレクトリ+
__init__.py
)
のどちらかを見つけた時点で読み込む.
したがって
my_project/ ← このディレクトリが sys.path に入る
├── main.py
├── my_package/ ← ここは「探す対象」であって sys.path には入らない
│ ├── __init__.py
│ ├── sub_a.py
│ └── sub_b.py
└── my_module.py
という構成で python3 main.py
を実行すると
/Users/.../my_project ← #0 ← スクリプトが置かれているディレクトリ
...
のように my_project だけが sys.path
に入り,その中にある my_package
は入らない.
でも import my_package
は問題なく成功しますよね??
理由は,Python が sys.path[0]
(今回なら /Users/.../my_project
)を開く
その直下に my_package
という名前の ディレクトリ+__init__.py
があるか調べる
という手順で検索するからです。
よくある勘違い
- 「パッケージが sys.path に入っていないと import 出来ない」
→ ルートディレクトリが入っていれば OK - sys.path.append('.../my_project/my_package') しないとダメ?
→ 不要。逆にやると import my_package.sub_a が壊れる場合がある
2. init.pyとは何か
それでは__init__.pyの色々を堀探ってみましょう.
2.1 パッケージ認識と初期化
- ディレクトリに
__init__.py
があると「このフォルダはパッケージ」とPythonが認識 - インポート時に一度だけ
__init__.py
を実行し,初期化処理(ログ設定や共通変数定義など)を行う
2.2 歴史的経緯と役割
- Python 2系では必須ファイル
-
Python 3.3以降はPEP 420で名前空間パッケージも可能になったが,明示的な初期化処理やAPI公開制御のために
__init__.py
が今も有用 - サブモジュールのまとめ読み込みや外部公開APIの制御にも使える
2.3 init.pyに書けること
- 空ファイルでもパッケージとして機能
- 初期化処理を記述して,import時に一度だけ実行
-
__all__
を定義して公開APIを制御 - サブモジュールをまとめてimportさせるエイリアスを作成
例えば以下のようにかける
import logging
logging.basicConfig(level=logging.INFO)
__all__ = ['sub_a', 'sub_b']
from .sub_a import ClassA
from .sub_b import function_b
2.4 相対インポートで構造化
同一パッケージ内のモジュールを相対パスでimportすると,名前衝突を避けられる
from .sub_a import ClassA # 同階層
from ..parent.sub_c import helper # 上位階層
3. Flaskプロジェクトでの__init__.pyの役割
3.1 エントリーポイントとしての位置付け
Flaskでは環境変数FLASK_APP
にパッケージ名を指定すると,そのパッケージの__init__.py
が最初に読み込まれる.ここにアプリ生成関数を定義すると,設定・拡張機能・Blueprintの初期化を一元管理できる.
3.2 create_app関数で起動処理をまとめる
from flask import Flask
from .config import Config
from .extensions import db, migrate
from .blueprints import main_bp
def create_app():
app = Flask(__name__)
app.config.from_object(Config)
db.init_app(app)
migrate.init_app(app, db)
app.register_blueprint(main_bp)
return app
4. ダンダー(double underscore)属性の特別扱い
4.1 命名規則と種類
前後にアンダースコア2つを付けた名前をダンダー(double underscore)属性と呼ばれ,Pythonのデータモデルに組み込まれている.
代表例は以下の6つ:
__init__
__name__
__repr__
__len__
__enter__
__exit__
4.2 Pythonインタプリタ内部での自動検出・呼び出しの使用例
__init__
と__repr__
の例
class MyClass:
def __init__(self, x):
self.x = x # インスタンス生成時に呼ばれる
def __repr__(self):
return f"MyClass(x={self.x})" # repr() や print() で使われる
obj = MyClass(10)
print(obj) # -> MyClass(x=10)
__len__
の例
class MyCollection:
def __init__(self, items):
self.items = list(items)
def __len__(self):
return len(self.items) # len() で返り値になる
col = MyCollection([1,2,3,4])
print(len(col)) # -> 4
__enter__
と__exit__
の例
class Resource:
def __enter__(self):
print("Enter")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("Exit")
with Resource() as r:
print("using resource")
# -> Enter
# using resource
# Exit
- **
__name__
の例
def main():
print("This is main")
if __name__ == "__main__":
main() # スクリプトとして実行されたときだけ呼び出される
これらのダンダー(double underscore)属性はPythonインタプリタが内部で自動的に検出・呼び出す仕組みを持っており,ユーザーコードは定義するだけでよい.
このように__init__.py
はPythonのモジュールシステムにおいて重要な役割を果たしていることがわかる.
__init__.py
を理解することで,Pythonのモジュールやパッケージの仕組みをより深く理解できるようになるでしょう!