これまで動いていたスクリプトやツールが、import pkg_resources の行で次のエラーを出して止まることがあります。
ModuleNotFoundError: No module named 'pkg_resources'
pkg_resources は、パッケージの情報を調べるために古くから使われてきたモジュールで、setuptools に同梱されて提供されてきました。このエラーは、その setuptools 側の事情で pkg_resources が手に入らなくなったために出ています。
そして、このエラーで検索すると多くのページが勧める pip install setuptools では、いまはもう戻りません。setuptools が pkg_resources を削除したからです。正しい直し方は、標準ライブラリの importlib.metadata に移行することです。
なぜ出るのか:setuptools が pkg_resources を削除した
pkg_resources が読み込めなくなる理由は2つあります。
setuptoolsがそもそも入っていない:python:3.12-slimのような軽量イメージや、setuptoolsを明示的に入れていない仮想環境では、pkg_resourcesも存在しません。- 新しい
setuptoolsがpkg_resourcesを削除した:setuptoolsは 82.0.0 でpkg_resourcesを取り除きました(errfix の実測では、81.0.0 には入っていて、82.0.0 以降ではModuleNotFoundErrorになります)。そのため、新しいsetuptoolsが入っていてもpkg_resourcesは手に入りません。
2番目が、このエラーでいちばん誤解されやすいところです。No module named 'distutils'(Python 3.12 で標準ライブラリから消えた)は pip install setuptools で戻せますが、pkg_resources は逆で、82.0.0 以降の setuptools には含まれないので、新しい版を入れても戻りません。同じ「setuptools 由来のモジュールが無い」でも、直し方が反対になります。
直し方:importlib.metadata に移行する
pkg_resources がしていたことの多くは、標準ライブラリの importlib.metadata(Python 3.8 以降に同梱)で置き換えられます。追加のインストールは要りません。よくある書き換えは次のとおりです。
# 旧:インストール済みパッケージの版を取る
import pkg_resources
v = pkg_resources.get_distribution("requests").version
# 新:標準ライブラリで
from importlib.metadata import version
v = version("requests")
用途ごとの対応は次のようになります。
pkg_resources(旧) | 置き換え |
|---|---|
get_distribution(name).version | importlib.metadata.version(name) |
parse_version(s) | packaging.version.parse(s)(packaging パッケージ。標準ライブラリではない) |
iter_entry_points(group) | importlib.metadata.entry_points(group=...)(Python 3.10 以降。3.9 以前は entry_points()[group]) |
resource_filename / resource_string | importlib.resources(files() など) |
working_set の列挙 | importlib.metadata.distributions() |
いくつか注意点があります。entry_points() は引数なしで呼ぶと全グループを返すので、グループで絞るなら上記のように group= を渡してください(Python 3.10 で API が変わり、3.9 以前は戻り値の辞書から [group] で引きます)。parse_version の移行先は importlib.metadata ではなく packaging.version.parse で、これは第三者の setup.py が from pkg_resources import parse_version している経路でよく踏みます。resource_filename のように実ファイルのパスが要る用途では、importlib.resources.files() は Traversable を返すため、with importlib.resources.as_file(files(pkg).joinpath(name)) as path: の形でパスを取り出します(files() は Python 3.9 以降)。
importlib.metadata は標準ライブラリなので、setuptools の有無に左右されません。軽量イメージでも、新しい setuptools の環境でも、同じように動きます。読み込んでいるコードが自分のものなら、上の対応で書き換えるのが恒久的な直し方です。
応急処置:setuptools を古い版に固定する
依存している第三者ライブラリが pkg_resources を使っていて、自分では直せない場合は、当面 setuptools を削除前の版に固定して動かせます。
pip install "setuptools<82"
ただし、これは据え置きにすぎません。pkg_resources は非推奨で、これから使えなくなっていく方向です。固定したままにすると、setuptools の新しい版から取り残されます。そのライブラリが pkg_resources を使わない新しい版を出していないかを先に確認し、あれば上げるほうが後を引きません。無いなら、この固定は「対応版が出るまで」の一時的な逃げ道と考えてください。
切り分け(うまくいかないとき)
pip install setuptoolsしたのに直らない:それがこのエラーの特徴です。setuptools82.0.0 以降はpkg_resourcesを含みません。pip show setuptoolsで版を確認し、importlib.metadataへの移行か、setuptools<82への固定に切り替えてください。- 自分では
pkg_resourcesを import していないのに出る:依存している第三者ライブラリやツールが内部で使っています。エラーのスタックトレースをたどると、どのパッケージがimport pkg_resourcesしているかが分かります。そのパッケージを新しい版に上げるか、上記のsetuptools<82で当面しのぎます。 pkg_resourcesが非推奨だというDeprecationWarningが出るが動いてはいる:まだpkg_resourcesが残っている(setuptoolsが 82.0.0 より前)状態です。動いてはいますが、上げると消えるので、いまのうちにimportlib.metadataへ移しておくと、setuptoolsを上げたときにこのエラーを踏みません。importlib.metadataでPackageNotFoundErrorになる:これは別の問題で、指定した名前のパッケージがインストールされていない、あるいは配布名とインポート名が違う(例:scikit-learnを入れてsklearnで調べようとした)ことが原因です。version()に渡すのは、インポート名ではなくpipで入れた配布名です。
検証環境
python:3.12-slim、ネットワーク有り- 再現:
setuptools==83.0.0を入れた状態でpkg_resources.get_distribution("pip")がModuleNotFoundError: No module named 'pkg_resources'を出して終了コード 1 - 修正:
from importlib.metadata import version; version("pip")が終了コード 0
再現から修正までは errfix の検証ハーネスが機械的に確認しています。新しい setuptools(83.0.0)が入っていても pkg_resources が読み込めないこと、importlib.metadata に移すと同じ処理が通ることを、再現→修正→シグネチャ消滅として通しました。setuptools が 82.0.0 で pkg_resources を削除したことは実測(81.0.0 には存在・82.0.0 以降で消失)で、詳細は setuptools の変更履歴に拠ります。