[初心者向け] Pythonのパッケージを自作して、パッケージとモジュールへの理解を深めてみる

Pythonチュートリアルの6章を終えたぐらいの方へ
2021.07.23

オペレーション部の加藤(早)です。

自分の話で恐縮ですが、
これまでプログラミングについては雰囲気で簡単なスクリプトくらいしか書いてこずだった私ですが
このたび業務でガッツリ開発に携わる必要がでたため
まずは基礎固めということでPythonチュートリアルを進めています。

現在は6章「モジュール」を完了したところで、
さらなる学習のためにPythonのパッケージを作ってみることにしました。 (この記事ではローカル環境に作成するまでで、配布のやりかたは書きません)

そんなレベル感の記事で、初心者向けですがご容赦ください。
同じく初学者のどなたかのお役に立てれば幸いです。

参考ドキュメント

Python モジュールの配布 — Python 3.9.4 ドキュメント

Packaging Python Projects — Python Packaging User Guide

1. モジュール — Python 3.9.4 ドキュメント > 6.4. パッケージ

Python の init.py とは何なのか - Qiita

モジュールとパッケージの違い

  • モジュール
    • Pythonの定義や文が入ったファイル(ファイル名 = モジュール名.py)。
  • パッケージ
    • モジュールをディレクトリにまとめたもの。

パッケージ化するモジュールを作成

パッケージ化するモジュール、mod1.py と mod2.py を作成しました。

mod1.py

def double(n):
    """引数の値を2倍にして出力"""
    print(n * 2)

mod2.py

def square(n):
    """引数の値を2条にして出力"""
    print(n**2)

最小限のパッケージ

モジュールをディレクトリに置けばそれはパッケージです。

mypackage/
    mod1.py
    mod2.py

このままPYTHONPATHが通っているディレクトリにパッケージを配置すればimport可能です。

$ python
Python 3.8.10 (default, May 11 2021, 08:09:02) 
[Clang 12.0.5 (clang-1205.0.22.9)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from mypackage.mod1 import double
>>> from mypackage.mod2 import square
>>> double(5)
10
>>> square(5)
25

ただしこの状態では少々物足りませんでした。
たとえば import <パッケージ名> 命令だけでは その下のモジュールを読み込んでくれないようです。

>>> import mypackage
>>> mypackage.mod1.double(10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'mypackage' has no attribute 'mod1'

初期化処理の定義(__init__.py)

パッケージ内に __init__.py を配置することで、パッケージimport時の初期化処理が定義できます。

mypackage/
    __init__.py
    mod1.py
    mod2.py

__init__.pyは空白でもかまいませんが、
今回はパッケージ内のモジュールをimportするように定義してみました。

__init__.py

from mypackage.mod1 import double as double
from mypackage.mod2 import square as square

mypackageをimportするだけでその下のモジュールを使えるようになりました。
asで別名を指定してインポートして、利用時にモジュール名(mod1, mod2)を割愛できるようにしています。

>>> import mypackage
>>> mypackage.double(5)
10
>>> mypackage.square(5)
25

また、 __init__.pyでは 変数 __all__を指定することで
from <パッケージ名> import * 構文で読み込む範囲を定義することができます。

__init__.py

__all__ = ["mod2"]

試しにmod2だけ指定して、mod1は読み込まないようにしてみました。

>>> from mypackage import *
>>> mod1.double(10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'mod1' is not defined
>>> mod2.square(10)
100

まとめ

シンプルなモジュールとパッケージを作ることで、
モヤッとした理解で使っていた言葉「パッケージ」と「モジュール」の違い、
パッケージの構成要素、パッケージをimportするときの挙動、等について簡単に知ることができました。

このままPythonを勉強して、boto3等、いつも使っているパッケージの__init__.pyを読み解くのが当面の目標です!