LoginSignup
8
4

More than 3 years have passed since last update.

pythonのパッケージ階層の話

Last updated at Posted at 2019-08-07

Overview

ふわっとしたパッケージ構造の理解を説明する試み。
python 3.7.2 および python 2.7.15 の場合です。

パッケージ階層

pythonはディレクトリ構造がそのままパッケージの階層になってます。

また、ディレクトリ直下の __init__.py はその階層に属するパッケージが最初に import された際に呼び出されます。

そのため、

  • 単体ファイル module
  • __init__.py だけ配置された module ディレクトリ

は同じ挙動になります。

そのため「最初は単体ファイルから書き始めて、コード量が増えてきたらディレクトリに切り出して __init__.py に移す」 といったことがスムーズにできるわけです。

単体ファイル module

$ tree
.
└── hoge.py
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
hoge/__init__.py
print("directory module")

from .m1 import method1
hoge/m1.py
def method1():
    print("directory method1")
$ python -c "import hoge; hoge.method1()"
directory module
directory method1

ちなみに

python2 では __init__.py は必須でした

$ tree foo/
foo/
└── bar.py
python2
$ 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 ではその縛りはなくなりました。

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.pyfoo/baz/__init__.py
    • __init__.py と ディレクトリmoduleの関係を思い出してください
  • 以下同じ
$ tree
foo/
├── __init__.py
├── a.py
├── bar
│   ├── __init__.py
│   ├── a.py
└── baz
    ├── __init__.py
    └── a.py
foo/bar/a.py
from ..baz import a

print(1111)
foo/baz/a.py
print(222)
$ python -c "import foo.bar.a"
222
1111

また、インタプリタ実行場所よりも上の階層は読めません。

python3
$ 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 全て走ります。

The Zen of Python

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

おわり。

8
4
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
8
4