追加ライブラリが必要な機能だけを任意に使わせたい場合は、その機能を呼ぶときまでimportを遅らせる。さらに、追加ライブラリそのものがない場合と、その内部で別の読み込みが壊れた場合を分ける。広いImportErrorをまとめて捕まえると、導入済みライブラリの不具合まで「未インストール」と誤案内してしまう。
基本機能まで追加依存に巻き込まない
集計は標準ライブラリだけで行え、グラフ機能だけに追加パッケージが必要なプログラムを考える。トップレベルでそのパッケージをimportすると、グラフを使わない人も読み込み段階で失敗する。任意機能の関数内で読み込めば、基本機能を独立して利用できる。
ただし、単にtry/except ImportErrorで囲むだけでは足りない。外部ライブラリは見つかったが、その内部が必要とする依存先だけ不足している場合もある。その状況を「任意ライブラリを入れてください」に変換すると、すでに入っているものを何度も入れ直すことになる。
例外のnameを調べる
次の例ではtip_optional_plotという練習用の追加機能を想定している。これは実在の製品名を推測してインストールさせるための名前ではない。実際のプログラムでは、利用する配布物の公式手順に沿った案内文へ変更する。
optional_feature.py
import importlib
def total(values):
return sum(values)
def draw(values):
try:
plugin = importlib.import_module("tip_optional_plot")
except ModuleNotFoundError as exc:
if exc.name != "tip_optional_plot":
raise
raise RuntimeError("Optional plotting feature is not installed") from exc
return plugin.render(values)
ModuleNotFoundErrorのnameが要求したトップレベル名と同じ場合だけ、利用者向けのRuntimeErrorへ変換する。それ以外はraiseだけで元の例外を再送出するため、失敗した依存先とトレースバックが残る。元の例外をfrom excでつなぐことも、原因を追う助けになる。
未導入・正常・内部の不足を確認する
以下は同じ関数を三つの状態で実行した結果である。追加機能がない状態、render関数を持つ正常な練習用モジュールがある状態、モジュール内部で別の不足名をimportする状態を一時フォルダーに作って確認した。
実行結果
missing: RuntimeError
available: plot:1,2
broken: tip_missing_backend_2026
内部依存の不足が、案内用のRuntimeErrorへ変わっていないことがポイントである。また、追加機能がない場合でもtotalは使える。必要な場所だけで読み込む設計の目的は、単にエラー表示を短くすることではなく、機能ごとの依存を分けることにある。
tryの範囲を小さくする
例ではtryの中にimportだけを置き、描画関数の実行は外側へ出した。もし実行中の処理まで囲むと、関数内部で発生した別のModuleNotFoundErrorを同じ不足判定へ巻き込む可能性がある。読み込み時の案内と、機能が実際に動くときの例外を区別した方が分かりやすい。
name属性による判定は実用的な目安だが、あらゆるライブラリの独自例外に対応する万能判定ではない。ライブラリ自身が任意依存をどのように扱っているかも確認する。要求名にドットを含む場合や、トップレベルと追加機能の配布が分かれている場合は、その構造に合わせた条件が必要になる。
見つかることと動くことは別である
find_specなどで事前に存在を調べても、内部の依存関係やバイナリ互換性まで保証できない。存在確認の直後に状態が変わる可能性もあるため、実際のimportで起こる失敗は適切に扱う必要がある。単純な有無のフラグをプログラム全体へ固定してしまう前に、使用時に確認できないかを考えたい。
また、ImportError以外にも、初期化時の設定不足や拡張モジュールの問題が異なる例外で表れることがある。この例はそれらを捕捉しないため、元のエラーをそのまま調べられる。異常を何でも空の結果へ置き換えると、利用者が正常終了だと誤解してしまう。
利用者に必要な操作だけを案内する
配布するライブラリなら、追加機能ごとの依存をextrasなどでまとめることもできる。その場合は、自分の配布名とextras名を正式に定義したうえで案内する。import名と配布名が同じとは限らないので、例外に出た名前をそのままコマンドへ埋め込まないようにする。
正常時に戻り値が得られることだけでなく、追加依存なしでも基本機能が使えること、内部の不具合が隠れないことを確認しよう。任意機能を後から追加するときも、この三つの状態を分けて考えると、既存機能への影響を小さくできる。
動作確認と参考資料
掲載例はLinux・CPython 3.12.14で動作確認した。OS固有のコマンドや環境ごとに変わるパスは、本文中の条件を確認して使ってほしい。
関連するTips
- pipで入れたのにimportできないときのトラブルシューティング
- 循環importをほどく:共通部分の切り出しで依存関係を整える
- import名とインストール名が違う?パッケージ情報を正しく確認する
