1つのファイルからモジュールのフォルダーへ
プロジェクトが大きくなると、1つのファイルでは足りなくなります。パッケージは、Python がひとまとまりとしてインポートできるモジュールのフォルダーです。フォルダーに __init__.py というファイル(空でかまいません)を置くと、中の .py ファイルがパッケージのモジュールになり、ドットで指定します。quant/pricing.py は quant.pricing、quant/data/loaders.py は quant.data.loaders です。パッケージ内のフォルダーはサブパッケージで、それぞれに __init__.py が必要です。
# A small project on disk
#
# quant-tools/
# pyproject.toml
# README.md
# quant/
# __init__.py
# pricing.py
# data/
# __init__.py
# loaders.py
# tests/
# test_pricing.py
# quant/pricing.py
def mid(bid, ask):
return (bid + ask) / 2
# anywhere else in the project
from quant.pricing import mid
import quant.data.loaders
print(mid(184.0, 185.0)) # 184.5拡張しやすい構成
インポートするコードはプロジェクト名のパッケージフォルダーに入れ、それ以外はその中ではなく隣に置きます。テストは tests/、プロジェクトを説明する README.md、名前と依存関係を書く pyproject.toml、データやノートブックはそれぞれ専用のフォルダーです。テストファイルは pytest などのツールが見つけられるよう test_*.py と名付けます。長い utils.py 1つより、pricing.py や loaders.py のように名前が明確で役割の絞られた小さなモジュールのほうが、テストも再利用も簡単です。
# pyproject.toml (names the project and its dependencies)
#
# [project]
# name = "quant-tools"
# version = "0.1.0"
# requires-python = ">=3.11"
# dependencies = ["numpy"]
# tests/test_pricing.py: found automatically because of the name
from quant.pricing import mid
def test_mid():
assert mid(184.0, 185.0) == 184.5パッケージ内でのインポート
パッケージ内では、最上位からの完全なパス(from quant.pricing import mid、絶対インポート)か、現在のモジュールからの相対(from .pricing import mid、同じパッケージならドット1つ、親ならドット2つ、相対インポート)でインポートできます。通常は絶対インポートのほうが分かりやすくなります。__init__.py はパッケージの最初のインポート時に実行されるので、利用者に必要な名前を再エクスポート(from .pricing import mid)でき、import quant; quant.mid(...) が使えます。パッケージのコードは python quant/data/loaders.py ではなく python -m quant.data.loaders で実行し、ドット付きの名前が解決されるようにします。
# quant/__init__.py: runs on the first "import quant"
from .pricing import mid # relative: same package
__all__ = ["mid"] # what "from quant import *" would give
# quant/data/loaders.py
from quant.pricing import mid # absolute: always starts at the top
from .. import pricing # relative: two dots = the parent package
# In a terminal, from the project folder:
# python -m quant.data.loaders (runs the module as part of the package)
# pytest -q (finds tests/test_*.py)# A file path and its module name are two spellings of one thing
path = "quant/data/loaders.py"
name = path[:-3].replace("/", ".")
print(name) # quant.data.loaders
print(name.split(".")[0]) # quant (the top-level package)
# __init__.py stands for its folder
print("quant/__init__.py"[:-3].replace("/", ".")) # quant.__init__ -> the package is "quant"__init__.py のないフォルダーを(「名前空間パッケージ」として)インポートできることもありますが、ツール、テストランナー、インストーラーの挙動が一定せず、自分のPCで動いたインポートが他の環境で失敗します。パッケージのフォルダーにはすべて __init__.py を置きましょう。ラボでは、コードがあるのに __init__.py がないフォルダーを見つけます。- パッケージは
__init__.pyのあるフォルダー。quant/data/loaders.pyはモジュールquant.data.loaders - コードはパッケージの中、テスト・README・
pyproject.toml・データはその隣に置く - 絶対インポートを優先し、パッケージのコードは
python -m package.moduleで実行する
次は仮想環境と pipです。プロジェクトごとに必要なライブラリだけを用意する方法を学びます。
ラボ:採点される内容
module_nameでファイルパスをモジュール名に変換する25点find_modulesでプロジェクトのインポート可能なモジュールを列挙する25点missing_initで __init__.py のないフォルダーを見つける25点import_lineでモジュールの import 文を作る25点