2.19. モジュールを書く

どんな .py ファイルもモジュールです。成長していくスクリプトをいくつかのファイルに分割すると、各ファイルが短く保たれ、共通のヘルパーをスクリプト間で共有できるようになります。

2.19.1. スクリプトを分割する

関連する関数のグループを、それら自身のファイルに取り出します。

camera_utils.py:

def banner():
    print("OpenMV")

def label(text):
    return "[" + text + "]"

main.py:

import camera_utils

camera_utils.banner()
print(camera_utils.label("ready"))

出力:

OpenMV
[ready]

2つのファイルは同じディレクトリ内に並んで置かれます。main.py が実行されると、import camera_utilscamera_utils.py を一度読み込み、そのトップレベルの文を実行し、結果として得られるモジュールオブジェクトを main 内の名前 camera_utils に束縛します。他のどこからの2回目の import camera_utils も、同じオブジェクトを返します。モジュールは最初の読み込み後にキャッシュされるためです。

モジュールの名前はそのファイル名に由来するので、camera_utils.pyimport camera_utils としてインポートされます。

2.19.2. 複数ファイルのモジュール(パッケージ)

モジュールは、単一の .py ではなく、ファイルの ディレクトリ であることもできます。ディレクトリの名前がモジュール名となり、その中のファイルがその サブモジュール となります。

camera_utils/
    __init__.py
    text.py
    timing.py

__init__.py は、パッケージ自体がインポートされたときに実行されるファイルです。空でもよく、サブモジュールから選ばれた名前を再エクスポートすることもできます。サブモジュールにはドット付きの名前でアクセスします。

import camera_utils.text
from camera_utils.timing import elapsed

camera_utils.text.label("ready")

パッケージの内部では、サブモジュールどうしは完全なドット付き名前でも、あるいは先頭のドットで「このパッケージ」を意味する 相対インポート でも、互いにアクセスできます。

camera_utils/timing.py:

from . import text             # the sibling submodule

def stamp(value):
    return text.label(str(value))

代わりに特定の名前を取り込むには、ドット付きの兄弟モジュールに続けてその名前を指定します。

from .text import label

def stamp(value):
    return label(str(value))

相対インポートはパッケージを自己完結に保ちます。パッケージのディレクトリ名を変更しても、すべてのサブモジュールを編集する必要はありません。

単一のファイルが快適なサイズを超えて成長したとき、または関連するモジュールのまとまりが1つの名前空間のもとに属するべきときには、パッケージを使います。日常的なスクリプトには、単一の .py ファイルで十分です。

2.19.3. __name__ガード

すべてのモジュールには、組み込みの名前 __name__ があります。その値は、ファイルがどのように使われているかによって変わります。

  • ファイルが 直接実行 される場合、__name__ は文字列 "__main__" に設定されます。

  • ファイルが別のスクリプトによって インポート される場合、__name__ はモジュール名、つまり .py を除いたファイル名に設定されます。

これを使ったイディオムは次のとおりです。

label_util.py:

def label(text):
    return "[" + text + "]"

if __name__ == "__main__":
    print(label("self-test"))

出力(直接実行した場合):

[self-test]

同じファイルが代わりに インポート される場合、__name__ はモジュール名に設定されるので、if ブロックはスキップされ、余分なものは何も実行されません。

>>> import label_util
>>> label_util.__name__
'label_util'

このパターンを使うと、ライブラリファイルに手早いスモークテストやデモを付けても、それをインポートするスクリプトを妨げずに済みます。