JEPA4Japan · チュートリアル

モジュール、パッケージ、仮想環境

2,222文字 6分で読めます #Python

コードを分割し、importを制御し、プロジェクトごとに依存関係を分離します。

コース進捗 コース目次 24レッスン中 24件を公開中

コードをラベル付きの引き出しへ入れよう

長いPythonファイルは、大きなおもちゃ箱のようです。全部入っていても、必要なものを探しにくくなります。Pythonでは、仕事ごとにラベル付きの引き出しを作れます。

  1. モジュール importできる1つの.py
  2. importパッケージ モジュールをまとめたフォルダー
  3. ディストリビューション pipでインストールする単位
  4. 仮想環境 1プロジェクト用のPythonの家
ファイルはコードを整理し、環境はインタープリターとインストール済み配布物を整理します。

似ていますが、同じものではありません。importはモジュールやimportパッケージを読み込みます。python -m pip install ...が入れる単位はディストリビューションです。1つのディストリビューションが複数のimportパッケージを提供することもあり、インストール名とimport名が一致する保証もありません。

名前をこぼさずにimportしよう

import statisticsは、1つのモジュールオブジェクトを名前へ結び付けます。ドットを見ると、道具の出所が分かります。

import statistics

minutes = [20, 30, 40]
print(statistics.mean(minutes))
print(statistics.median(minutes))

出力:

30
30

このモジュール接頭辞が名前空間です。自分のmeanとstatistics.meanを同時に使えます。必要な名前だけを読み込む書き方もあります。

from statistics import mean as average

print(average([10, 20, 30]))

別名は意味が明確になるときだけ使います。from statistics import *は避けましょう。ワイルドカードでは、どの名前が入り、どの古い名前が置き換わったか見えません。

1つのPythonプロセスで最初にモジュールを正常にimportすると、最上位のコードが上から下へ実行されます。その後の通常のimportは、キャッシュされた同じモジュールを使います。最上位には主に関数、クラス、定数の定義を置き、入力やアプリ本体を始めないようにします。

  1. 最初のimport 最上位の定義を実行
  2. 覚える モジュールをキャッシュ
  3. mainガード 意図したときだけアプリを実行
importは定義を利用可能にし、mainガードはスクリプト専用処理を守ります。
def main():
    print("学習レポートを開始します。")


if __name__ == "__main__":
    main()

入口のファイルとして実行すると、__name__は"__main__"なので表示されます。importされた場合はモジュールのimport名になり、main()は使えるようになりますが、自動実行されません。

パッケージに1つの玄関を作ろう

通常のパッケージは、__init__.pyと関連モジュールを含むディレクトリです。

project/
├── study_report/
│   ├── __init__.py
│   ├── calculations.py
│   └── __main__.py
└── tests.py

__init__.pyは通常のパッケージであることを示し、最初のパッケージimport時に実行されます。空でもよく、小さな公開インターフェースを再公開してもかまいません。パッケージ内の先頭のドットは「このパッケージから」という意味です。

from .calculations import summarize

__all__ = ["summarize"]

__all__は意図するインターフェースを説明しますが、privacyやsecurityの壁ではありません。AがBをimportし、BがAをimportする循環importも避けます。共有処理を低い層へ移すか、より明確な境界で値を渡してください。

  1. import __init__.pyがAPIを開く
  2. 相対import ドットが文脈を保つ
  3. python -m __main__.pyを実行
パッケージにはimport用の玄関と、必要なら実行用のボタンがあります。

パッケージを含むディレクトリから実行します。

python -m study_report

python study_report/__main__.pyでは内部ファイルを単独のスクリプトとして扱うため、相対importが壊れることがあります。

実行できる学習レポートパッケージを作ろう

上の構成を作り、study_report/calculations.pyへ保存します。

def summarize(sessions):
    totals = {}
    for topic, minutes in sessions:
        totals[topic] = totals.get(topic, 0) + minutes
    return sorted(totals.items())

study_report/__init__.py:

from .calculations import summarize

__all__ = ["summarize"]

study_report/__main__.py:

from . import summarize


def main():
    sessions = [("Python", 45), ("Git", 40), ("Python", 30)]
    print("学習レポート")
    for topic, minutes in summarize(sessions):
        print(f"- {topic}:{minutes}分")


if __name__ == "__main__":
    main()

パッケージと同じ場所のtests.py:

from study_report import summarize

assert summarize([]) == []
assert summarize([("Python", 20), ("Python", 30)]) == [("Python", 50)]
print("テストに合格しました。")

python tests.py、続けてpython -m study_reportを実行します。期待される出力:

テストに合格しました。
学習レポート
- Git:40分
- Python:75分

テストのimportではレポートが表示されません。パッケージは安定した操作を1つ公開し、見えるアプリ動作は__main__.pyが担当します。

出所を確認し、プロジェクトへ家を用意しよう

ローカルのstatistics.py、json.py、pathlib.pyが、本物のライブラリを隠すことがあります。Pythonが読んだ場所を調べます。

import statistics

print(statistics.__name__)
print(statistics.__file__)

2行目はマシンによって違います。自分のプロジェクトではなく、期待するライブラリを指すか確認してください。アプリ各所でsys.pathを書き換えず、ファイル名や構成を直します。

プロジェクトルートで分離環境を作ります。

python -m venv .venv
source .venv/bin/activate
python -c "import sys; print(sys.prefix != sys.base_prefix)"

Windows PowerShellは.venv\Scripts\Activate.ps1です。確認結果はTrueになります。必要なディストリビューションはpython -m pip install distribution-nameで入れ、deactivateで終了します。.venv/を.gitignoreへ加え、環境を移動・コミットせず、記録した手順から作り直します。venvが分離するのはインストール済み配布物であり、データファイルやimport設計ではありません。

3つの小さなチャレンジと次への準備

  1. 安全なimport。 温度変換をconversions.pyへ移し、表示をweather_report.pyのmainガード内へ置きます。importだけでは何も表示されないことを確認します。
  2. 新しいパッケージ。 公開する集計関数1つ、__init__.py、__main__.pyを持つreading_logを作り、python -m reading_logで動かします。
  3. 環境の証明。 .venvを作って有効化し、prefixの確認がTrueになることを確かめ、無効化してGitの対象外だと確認します。

注意点:ワイルドカードは名前を隠し、最上位の実処理はimportの副作用になります。内部ファイルの直接実行はパッケージ文脈を失い、ローカルファイル名は別のモジュールを読ませることがあります。

  • モジュール、importパッケージ、ディストリビューション、venvを区別できる
  • 名前空間と最初のimport時の実行を説明できる
  • mainガードと小さな__init__.pyインターフェースを使える
  • python -mでパッケージを実行し、テストできる
  • importしたモジュールの出所を調べられる
  • .venvを作成、確認、終了、再作成できる
  • 3つの小さなチャレンジを終えた

次章では、pathlib、with、CSV、JSONを使い、整理したコードへ安全なローカル保存を加えます。