きっかけ
サブパッケージからimportする際に、パッケージとして認識させるために__init__.pyが必要だと思っていたがチーム内のレビューで可能であれば削除してほしいとコメントをもらったことをキッカケに深堀しました。
Python 3.3以降
Python 3.3 (2012年):PEP 420: implicit Namespace Packages(暗黙の名前空間パッケージ)
→ 明示的に__init__.pyでパッケージ定義しなくても自動的にサブパッケージを探索してくれるようになった
2025年10月時点では、Python 3.13が最新バージョンなのでほとんどのケースがこのPython3.3以降のケースにあたると思われます。
Python 2.3 ~ 3.2
Python 2.3 (2003年):標準ライブラリpkgutilモジュールのpkgutil.extend_path()関数で名前空間パッケージを拡張
→ サブパッケージに以下のような__init__.pyを使うことで名前空間パッケージを拡張していた
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
この方法はPython 2.3 ~ Python 3.2までのバージョン互換性に対応したい場合に今でも使われるようでした。
参考:名前空間パッケージをパッケージする - Python Packaging User Guide
Python 2.2以前
Python 2.2以前:外部ライブラリであるsetuptoolsのpkg_resources.declare_namespace関数で名前空間パッケージを拡張
→ サブパッケージに以下のような__init__.pyを使うことで名前空間パッケージを拡張していた
__import__('pkg_resources').declare_namespace(__name__)
この方法は非推奨で過去のPythonバージョンをサポートせざるを得ないシーンで使われるレガシーなやり方のようでした。
参考:名前空間パッケージをパッケージする - Python Packaging User Guide
Python 3.2と3.3での動作確認
pyenvで過去のPythonバージョンを実行しようとすると、OpenSSLの古い依存ライブラリを使うためビルドエラーになってしまった。
なので、古い依存ライブラリでビルドするDockerライブラリを準備して動作確認を行なっています。
GitHub - buzzword111/python-3.2-3.3-imports: レガシー Python (3.2.6 / 3.3.0) を Docker 上でビルド・実行するサンプル