Overview
ふわっとしたパッケージ構造の理解を説明する試み。
python 3.7.2 および python 2.7.15 の場合です。
パッケージ階層
pythonはディレクトリ構造がそのままパッケージの階層になってます。
また、ディレクトリ直下の __init__.py
はその階層に属するパッケージが最初に import
された際に呼び出されます。
そのため、
- 単体ファイル module
-
__init__.py
だけ配置された module ディレクトリ
は同じ挙動になります。
そのため「最初は単体ファイルから書き始めて、コード量が増えてきたらディレクトリに切り出して __init__.py
に移す」 といったことがスムーズにできるわけです。
単体ファイル module
$ tree
.
└── hoge.py
print("file module")
def method1():
print("file method1")
$ python -c "import hoge; hoge.method1()"
file module
file method1
ディレクトリ module
$ tree
.
└── hoge
├── __init__.py
└── m1.py
print("directory module")
from .m1 import method1
def method1():
print("directory method1")
$ python -c "import hoge; hoge.method1()"
directory module
directory method1
ちなみに
python2 では __init__.py
は必須でした
$ tree foo/
foo/
└── bar.py
$ python
Python 2.7.15 (default, Mar 19 2019, 13:35:07)
[GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.10.44.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import foo.bar
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named foo.bar
が、python3 ではその縛りはなくなりました。
$ python
Python 3.7.2 (default, May 16 2019, 10:59:10)
[Clang 10.0.0 (clang-1000.10.44.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import foo.bar
相対 import
import を実行する場所からみて、 .
を並べる数で上の階層を辿っていきます。
-
.
がその階層の他のパッケージ- 下図で言うと
foo/bar/__init__.py
から見たfoo/bar/a.py
- 下図で言うと
-
..
で1階層上- 下図で言うと
foo/bar/__init__.py
から見たfoo/a.py
やfoo/baz/__init__.py
-
__init__.py
と ディレクトリmoduleの関係を思い出してください
- 下図で言うと
- 以下同じ
$ tree
foo/
├── __init__.py
├── a.py
├── bar
│ ├── __init__.py
│ ├── a.py
└── baz
├── __init__.py
└── a.py
from ..baz import a
print(1111)
print(222)
$ python -c "import foo.bar.a"
222
1111
また、インタプリタ実行場所よりも上の階層は読めません。
$ cd foo/bar/ && python -c "import a"
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/Users/uu147969/workspace/qiita/foo/bar/a.py", line 1, in <module>
from ..baz import a as baza
ImportError: attempted relative import with no known parent package
__init__.py
に import を書く場合
※以下ポエム要素あり
__init__.py
に __all__
を置くことで、その module で何を公開するかを指定する事ができます。
https://qiita.com/suzuki-kei/items/8fea67655abf216a5013
これを、パッケージの最上位の __init__.py
に置いて、実質パッケージ内の全ての module を import
するケースがよくありますが、これはあまりおすすめできません。
前述した通り、その module を触った(最初に import
しようとした)時点で __init__.py
の内容が走るので、芋づる式に必要のない module まで import
されてしまうためです。
また、 import foo.bar.a
とした場合にも途中のスキップはできず、 foo/__init__.py
foo/bar/__init__.py
全て走ります。
Explicit is better than implicit.
Namespaces are one honking great idea -- let's do more of those!
とあります。
from foo import *
とかやってしまうと namespace が台無しになる上、「この変数どこで定義されてるの?」「もしくはどこで import されたの?」の切り分けが難しくなります。
module 分割は粒度が難しいところですが、論理的に意味を成す単位で切り出して、利用する側で明示的に必要な module を import
するような実装を心掛けるのが良いと思います。
参考: module 設計の具体例
python2 の urllib が、場当たり的に対応してたらこうなっちゃった残念感満載だったところを、
https://github.com/python/cpython/blob/2.7/Lib/urllib.py
https://github.com/python/cpython/blob/2.7/Lib/urllib2.py
https://github.com/python/cpython/blob/2.7/Lib/urlparse.py
python3 で綺麗に書き直されています。
https://github.com/python/cpython/tree/3.7/Lib/urllib
おわり。