Python 3.11 までは動いていたスクリプトやツールが、3.12 に上げた環境で次のエラーで止まることがあります。
ModuleNotFoundError: No module named 'distutils'
コードは何も変えていないのに、import distutils(や from distutils.util import strtobool のような読み込み)が急に見つからなくなります。原因は、distutils が Python 3.12 で標準ライブラリから削除されたことです。あなたのコードではなく、Python 側から機能が1つ無くなったために出ています。
急いで直すなら、pip install setuptools で戻せます。ただし「なぜ消えたのか」「なぜ setuptools で戻るのか」を押さえておくと、同じ環境で繰り返さずに済みます。
なぜ手元(3.11 以前)では出ないのか
distutils はパッケージのビルドや設定に使われてきた古い標準ライブラリで、Python 3.12 で削除されました(PEP 632 で予告されていた撤去です)。ここが境界です。
- Python 3.11 以前 …
distutilsは標準ライブラリに含まれていました。だからimport distutilsは、追加のインストールなしにそのまま通っていました。 - Python 3.12 以降 … 標準ライブラリから
distutilsが無くなりました。import distutilsが通るのは、setuptoolsがインストールされていて、その複製を提供しているときだけです。
「3.11 では通っていた」のは、標準ライブラリに distutils があったからです。3.12 でそれが消え、いまや distutils の供給源は setuptools に移りました。開発機や以前の CI(3.11)では出ないのに、3.12 に上げたクリーンな環境(新しい venv、python:3.12-slim などの最小イメージ、CI)でだけ No module named 'distutils' が出る、というすれ違いになります。落ちている環境の python --version と、setuptools が入っているか(pip show setuptools)を先に確認してください。
直し方:setuptools を入れる
setuptools は、自前に distutils の複製を持っていて、import distutils をその複製へ振り向ける仕組み(_distutils_hack)を備えています。3.12 以降で distutils が要るなら、これを入れます。
pip install setuptools
これで import distutils も from distutils.util import strtobool も通るようになります。setuptools は活発に更新されているパッケージですが、新しい版(この記事の確認時点の最新でも)でも distutils の複製は提供されているので、版を古く固定する必要はありません。
恒久的には、distutils の import 自体をやめるのが本筋です。distutils は削除済みで、今後も標準ライブラリには戻りません。いま setuptools 経由で動いているのは、あくまで互換のための複製です。自分のコードなら、用途に応じて置き換えられます。
- バージョン比較の
distutils.version.LooseVersion→packagingのpackaging.version.Version。ただしVersionは PEP 440 準拠で、非準拠の版文字列を渡すとInvalidVersionを投げます。緩いパースに頼っていた箇所は挙動差に注意してください。 distutils.util.strtobool→ 真偽の変換を自前の小さな関数に置き換える。元のstrtoboolは大文字小文字を無視してyes/true/on/1などを真、no/false/off/0などを偽とし、それ以外はValueErrorを投げ、戻り値は1か0(int)です。同じ判定が要るなら、この契約(真の集合・偽の集合・不正値は例外・戻り値は 1/0)を踏襲してください。distutils.core.setupによるパッケージング →setuptoolsの API(from setuptools import setup)や、pyproject.tomlベースのビルドへ。
依存しているのが自分では直せない第三者パッケージの場合は、そのパッケージが 3.12 に対応した新しい版を出していないかを先に確認します。無ければ、当面は setuptools を入れて動かしつつ、対応版が出たら乗り換えるのが現実的です。
版・環境の境界
errfix の検証ハーネスで実測した点です。同じスクリプト(from distutils.util import strtobool)を、環境だけ入れ替えて動かしました。
| 環境 | import distutils |
|---|---|
| Python 3.11(標準ライブラリに distutils あり) | 通る |
| Python 3.12 / 3.13(setuptools 無し) | No module named 'distutils' |
Python 3.12 / 3.13(pip install setuptools 後) | 通る(setuptools の複製が提供) |
(削除そのものは PEP 632 に基づきます。setuptools が複製を提供する挙動は、確認した版の範囲での実測です。「あらゆる setuptools 版で提供される」とまでは言えないので、動かない場合は下の切り分けを見てください。なお python:3.11-slim のように setuptools が同梱された環境では、3.11 でも import distutils は setuptools の複製経由で解決されます。stdlib と複製のどちらでも通るため、表の「通る」は変わりません。)
切り分け(うまくいかないとき)
No module named 'pkg_resources'で来た(似た見出しの別エラー):pkg_resourcesもsetuptools由来のモジュールですが、直し方は逆で、pip install setuptoolsを新しく打っても戻りません(setuptools82.0.0 で削除されたためです)。No module named ‘pkg_resources’ の直し方 を参照してください。pip install setuptoolsしても直らない:そのpipが、落ちているpythonと同じ環境を指しているかを確認してください。仮想環境が有効になっていない、あるいは複数の Python が混在していると、別の環境に setuptools を入れていることがあります。python -m pip install setuptoolsのように、落ちているpythonからpipを呼ぶと取り違えを避けられます。pip installの途中で別のパッケージがNo module named 'distutils'で失敗する:第三者パッケージのsetup.py(ビルドスクリプト)がdistutilsを読み込んでいて、3.12 のビルド環境にそれが無い、という形です。多くはそのパッケージを 3.12 対応の新しい版に上げると解決します。上げられない事情があるなら、ビルド環境にsetuptoolsを用意します。No module named 'distutils.msvccompiler'のように、サブモジュール名まで付いている:distutils全体ではなく、その中の一部を読み込もうとしています。setuptools の複製に存在しないサブモジュールだと、setuptools を入れても解決しません。読み込んでいる側のコードが、削除された(あるいは複製に含まれない)内部モジュールに依存していないかを確認してください。ImportError: cannot import name '...' from 'distutils...'(No module namedではない):distutils自体は見つかっているが、その中の特定の名前が無いケースです。本記事のNo module named(モジュールごと無い)とは別症状で、読み込もうとしている関数・クラスが(setuptools の複製の版差などで)存在しないことが多く、別の切り分けになります。実際に読み込まれているdistutils.__file__とsetuptoolsの版を確認してください。- 警告として
DeprecationWarning: distutils Version classes are deprecatedが出るだけで動いている:まだ落ちてはいませんが、distutilsに依存している合図です。3.12 に上げる前に、上記の恒久策(packagingなどへの置き換え)を進めておくと、移行時にこのエラーを踏みません。
検証環境
python:3.12-slim(再現)/pip install setuptools後(修正)。python:3.11-slimでは同じスクリプトが通ることも確認- 再現:
from distutils.util import strtobool→ModuleNotFoundError: No module named 'distutils'で終了コード 1 - 修正:
pip install setuptoolsの後、同じスクリプトがstrtobool('yes')を正常に評価して終了コード 0
再現から修正までは errfix の検証ハーネスが機械的に確認しています。3.12 のクリーンな環境で distutils の読み込みが落ちること、setuptools を入れると同じコードが通ることを、再現→修正→シグネチャ消滅として通しています。3.12 で distutils が標準ライブラリから外れたこと自体は PEP 632 に拠ります。