Python 3.12 に上げてから、import pkg_resources(あるいは pkg_resources を使うツールの起動)で次のエラーが出て止まることがあります。
AttributeError: module 'pkgutil' has no attribute 'ImpImporter'. Did you mean: 'zipimporter'?
トレースバックをたどると、pkg_resources/__init__.py の中で pkgutil.ImpImporter を参照しているところで落ちています。これは、Python 3.12 が pkgutil.ImpImporter を削除したのに、入っている setuptools(の pkg_resources)が古くてそれをまだ使っているために起きます。
なぜ 3.12 で落ちるのか
pkgutil.ImpImporter は古い import 機構(imp モジュール)に連なる仕組みで、Python 3.3 で非推奨になり、3.12 で削除されました。代わりが用意されたわけではありません。3.12 では ImpImporter も ImpLoader も、その土台だった imp モジュール自体も無くなっています。一方、古い setuptools に同梱される pkg_resources は、読み込まれるときに pkgutil.ImpImporter を登録しようとします。3.11 まではその属性があったので通っていましたが、3.12 では無くなったため、import pkg_resources の途中で AttributeError になります。
pkg_resources は setuptools が提供するモジュールなので、直すべきは Python でも自分のコードでもなく、setuptools の版です。
なぜ Python 3.12 で出るのか
同じ古い setuptools でも、Python 3.11 までなら通ります(pkgutil.ImpImporter がまだあるため)。エラーになるのは Python を 3.12 に上げた後です。手元やこれまでの環境が 3.11 以前なら踏みません。3.12 に上げたのに setuptools が古いまま、という組み合わせで表面化します。setuptools が古いまま残るのは、次のような場面です。
- どこかで明示的に古い setuptools が入っている:Python 3.12 の標準 venv(
python -m venv)は、ensurepipの変更で既定では setuptools を seed しません。ですから素の 3.12 venv でこの症状が出るなら、依存でのピン、以前の環境からの持ち越し、ビルド隔離のrequires、サードパーティ virtualenv の seed キャッシュなど、どこかで古い setuptools を明示的に入れている、と切り分けられます。 requirements.txtや制約ファイルで setuptools を固定している:再現性のために古い版をピンしていると、3.12 でだけ落ちます。- 古い pip / CI イメージ / tox:pip が古いと seed される setuptools も古くなります。
直し方1:setuptools を 3.12 対応版へ上げる(ただし窓に注意)
基本は setuptools を新しくすることです。3.12 対応版(66.1 以降)なら pkg_resources が pkgutil.ImpImporter を参照しなくなっています。
pip install --upgrade "setuptools>=66.1"
ただし、上げすぎにも境界があります。setuptools は後の版(67.5 以降で非推奨、82 で削除)で pkg_resources 自体を同梱しなくなりました。pkg_resources を使うコードやツールを動かしたいのに最新へ上げると、今度は No module named 'pkg_resources' に変わります。import pkg_resources を通したいなら、3.12 に対応しつつ pkg_resources をまだ含む版(おおよそ 66.1〜81 の窓)に収めます。
pip install "setuptools>=66.1,<82"
pip install --upgrade setuptools だけで最新へ跳ぶと、エラーが ImpImporter から pkg_resources 不在へすり替わることがあります。今 pkg_resources が要るのかどうかで、上げ先を決めてください。
直し方2:pkg_resources から離れる/古い供給元を直す(恒久)
pkg_resources は非推奨で、将来の setuptools では外れていきます。版の窓で凌ぐのは当座の措置なので、根の方も見ておきます。
- 自分のコードが
pkg_resourcesを使っているなら移行する:バージョン取得はimportlib.metadata、同梱データの読み出しはimportlib.resourcesに置き換えられます(どちらも標準ライブラリ)。移行すれば版の窓を気にせずに済みます。 - 古い setuptools がどこから来るかを断つ:仮想環境を作り直す、
python -m venvを新しい Python で張り直す、pip install --upgrade pip setuptoolsを環境構築の最初に入れる、といった形で seed を新しくします。 - 落ちているのが第三者ツールの
pkg_resourcesimport なら、そのツールを新しくする:ツール側が pkg_resources 依存をやめている新版があることが多いです。
切り分け(うまくいかないとき)
- setuptools を上げたら、今度は
No module named 'pkg_resources'になった:上げすぎです。pkg_resourcesを含む版(66.1〜81 の窓)へ下げます。詳細は No module named ‘pkg_resources’ を参照してください。 No module named 'distutils'が出ている:これは別で、3.12 が distutils を削除したことによるものです。対処は No module named ‘distutils’ を参照してください(同じ 3.12 の標準ライブラリ削除の一族です)。- setuptools を上げたのに、まだ古い版が使われる:仮想環境の外側(システム側)を直していないか、環境を有効化していない可能性があります。
python -c "import setuptools; print(setuptools.__version__)"で実際に使われている版を確かめます。 pip installの途中(ビルド段)で出ている:PEP 517 のビルド隔離では、pip が別環境を作って pyproject の[build-system].requiresに従い setuptools を入れ直すため、外側の setuptools を上げても直らないことがあります。対象プロジェクトのrequiresや lock を対応版にするか、対応版を固定してください(トレースバックにpip-build-envが出るのが目印です)。
検証環境
python:3.12-slim、ネットワーク有り- 再現:
pip install "setuptools==65.5.1"の後にimport pkg_resourcesすると、module 'pkgutil' has no attribute 'ImpImporter'を出して終了コード 1 - 修正:
pip install "setuptools==75.6.0"の後にimport pkg_resourcesすると、終了コード 0
再現から修正までは errfix の検証ハーネスが機械的に確認しています。境界が Python 3.12 であることは、対照として python:3.11-slim(同じ setuptools==65.5.1)で import pkg_resources が通ることを確かめて裏づけました。上げすぎると pkg_resources 不在に変わる点も実機で確認しています(機械検証の fix は pkg_resources を含む 75.6.0)。エラー文言は実機出力から引用しています。virtualenv の seed や CI の供給元については、機械検証の対象は上記に限り、一般的な引き金として記述しています(pkgutil.ImpImporter の削除は CPython 3.12 のリリースノートに拠ります)。