パッケージに入れたテンプレートを、どこから実行しても読めるようにしたいならimportlib.resources.files()を使う。現在の作業フォルダーを基準にする相対パスでは、実行場所が変わると見つからなくなる。また、リソースが常に通常のファイルとして置かれているとも限らない。パッケージを基準に参照すれば、配置に依存する部分を減らせる。
同梱データと利用者のデータを分ける
ここで扱うのは、ライブラリと一緒に配布する読み取り用のテンプレートである。利用者が入力するCSVや、解析結果の保存先とは役割が違う。利用者のファイルは引数や設定で場所を受け取り、パッケージ内部へ書き込むことを前提にしない方が扱いやすい。
例としてtip_dataという小さなパッケージにtemplate.txtを入れる。src/tip_data/__init__.pyは空でよい。template.txtの内容はsample={name}という一行にする。ライブラリの機能に必要なデータなので、配布物にも確実に含める必要がある。
配布物にデータを含める
setuptoolsを使う場合の最小例を示す。pyproject.tomlのpackage-dataで、tip_dataにある.txtをwheelへ含める。この設定がないと、ソースの置き場では読めたのに、インストール後にはなくなるという違いが起こり得る。
pyproject.toml
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[project]
name = "tip-data"
version = "0.1.0"
[tool.setuptools.packages.find]
where = ["src"]
[tool.setuptools.package-data]
"tip_data" = ["*.txt"]
ビルド方式によって同梱データの設定方法は異なる。importlib.resourcesはすでに含まれているリソースを読む仕組みであり、欠けたファイルを自動で配布物へ追加する仕組みではない。配置とビルド設定の両方を確認しよう。
パッケージ名を基準に読む
read_template.py
from importlib.resources import files
text = files("tip_data").joinpath("template.txt").read_text(encoding="utf-8")
result = text.format(name="A").strip()
print(result)
assert result == "sample=A"
実行結果
sample=A
filesへ対象パッケージを位置引数で渡し、joinpathでリソースを選ぶ。read_textには文字コードを明示する。この例では、読み込んだテンプレートへ試料名Aを埋め込み、末尾の改行だけを整えて表示している。
filesはPython 3.9以降のAPIである。戻り値は通常のPathとよく似た操作ができるが、ファイルシステム上のパスそのものとは限らない。とりあえずPath(…)へ変換するのではなく、read_textやopenなどのリソース操作を利用する。
実ファイルのパスを要求する処理へ渡す
外部ライブラリがファイルパスしか受け付けない場合は、as_fileで一時的に実ファイルとして扱える。使う処理をwithの内側で完了させることが重要である。文脈を抜けた後も一時パスが存在し続けると考えないようにする。
use_path.py
from importlib.resources import as_file, files
resource = files("tip_data").joinpath("template.txt")
with as_file(resource) as path:
text = path.read_text(encoding="utf-8")
assert text == "sample={name}\n"
普通のインストールでは既存のファイルパスが使われる場合もあるが、それに依存しないコードにしておく。パスをオブジェクトに保存して後から読むライブラリへ渡す場合は、いつ読み込みが完了するかを確認し、withの有効範囲を決める必要がある。
インストールした状態で確かめる
この記事の例では、練習用パッケージをwheelへビルドして独立venvへ通常インストールし、ソースと異なる作業フォルダーから二つの読み込み例を実行した。ソース内にファイルがあることだけではなく、配布物経由で読み込めることを確認している。
実際のプロジェクトでも、ソースのルートから実行する確認だけで済ませない方がよい。開発中のeditable installではソースが直接見えるため、通常の配布物に含め忘れたファイルでも読めてしまう場合がある。通常インストールで確かめると、この違いを発見しやすい。
なお、filesで指定したパッケージが見つからなければ、まずインストール先と実行中のPythonを照合する。パッケージは見つかるのにtemplate.txtだけがないなら、名前の大文字・小文字、package-dataの対象、作成したwheelの内容を順に確認する。
同梱リソースは、通常はプログラムの一部として読み取り専用で使う。テンプレートを利用者が変更できるようにしたい場合は、利用者用フォルダーへ明示的にコピーし、その後は外部ファイルとして管理すると、アップデート時の扱いも分かりやすい。
動作確認と参考資料
掲載例はLinux・CPython 3.12.14で動作確認した。OS固有のコマンドや環境ごとに変わるパスは、本文中の条件を確認して使ってほしい。
