これまで動いていたスクリプトが、import matplotlib(や import pandas)の時点で次のエラーを出して止まることがあります。
A module that was compiled using NumPy 1.x cannot be run in
NumPy 2.0.0 as it may crash. ...
AttributeError: _ARRAY_API not found
ImportError: numpy.core.multiarray failed to import
パッケージによっては、代わりに次の形で出ます。
ValueError: numpy.dtype size changed, may indicate binary incompatibility. Expected 96 from C header, got 88 from PyObject
どちらも原因は同じです。numpy を使っている C 拡張ライブラリが、いま入っている numpy とは違うバージョン向けにビルドされているために起きます。多くの場合、引き金は numpy が 2.0 に上がったことです。
直し方は、壊れているライブラリを numpy 2 に対応した版に上げるか、numpy を 1.x に戻すかのどちらかです。
なぜ起きるのか:numpy 2.0 が C の互換(ABI)を変えた
matplotlib や pandas のように numpy を使うライブラリ(scipy、scikit-learn など、C 拡張を持つ科学計算系ライブラリの多くが該当します)は、内部に C 拡張を持っていて、ビルドした時点の numpy の内部構造(ABI)に合わせてコンパイルされています。numpy は 2.0(2024年)で、この C レベルの互換を大きく変えました。
そのため、numpy 1.x でビルドされた C 拡張を numpy 2.x で読み込むと、想定していた内部構造と食い違って壊れます。A module that was compiled using NumPy 1.x cannot be run in NumPy 2.x は、numpy 2.x 自身が「これは numpy 1.x 向けにビルドされた拡張だ」と検出して出す文言です。numpy.dtype size changed のほうは、C から見た構造体の大きさが食い違っていることを直接示しています。名前は違っても、指しているのは同じズレ(C 拡張と実行時 numpy の ABI 不整合)です。numpy 公式のトラブルシューティングも、この2つの文言を「下流パッケージが非互換な形でビルドされた」という同じ原因の下にまとめています(NumPy troubleshooting)。なお numpy.dtype size changed 自体は numpy 2.0 で新設された文言ではなく、C 拡張とその実行時 numpy の版がずれたとき全般に出る一般的なメッセージで、numpy 2.0 でこれが再び広く出るようになりました。
なぜ手元では出なかったのか
以前に環境を作ったときは、numpy がまだ 1.x で、ライブラリのビルドと一致していました。ところが、numpy を上げた、環境を作り直したら最新の numpy 2.x が入った、requirements.txt に numpy の上限を書いていない、といった状況で numpy 2.x が入ると、古いままのライブラリが合わなくなります。
引き金になりやすいのは、ライブラリ側が numpy に上限を書いていないことです。たとえば古い matplotlib は numpy を「ある版以上」とだけ要求していて、「2.0 より上はだめ」とは書いていません。そのため pip install は numpy 2.x を正当な選択として入れてしまい、ResolutionImpossible のような衝突にもなりません。install は成功し、壊れているとわかるのは import の瞬間です。まず、落ちている環境で pip show numpy と、落ちているライブラリの版を突き合わせてください。
直し方:numpy 2 に合わせてそろえる
方向は2つあります。
壊れているライブラリを、numpy 2 に対応した版に上げる(前に進める)。 主要なライブラリの多くは、すでに numpy 2 対応版を出しています。ライブラリ側を上げれば、numpy 2.x でビルドされた拡張が入り、ズレが消えます。
pip install --upgrade matplotlib numpy
上のコマンドは matplotlib の例です。numpy に依存するライブラリは複数あるので、落ちているものを列挙して numpy ごと一緒に上げるのが確実です。1つだけ上げても、別の古いライブラリが同じエラーで落ちることがあります。
numpy を 1.x に戻す(据え置く)。 どうしても上げられない依存があるなら、numpy を 2.0 より前に固定します。
pip install "numpy<2"
requirements.txt を使っているなら、numpy<2 のように上限を明示して書いておくと、環境を作り直しても numpy 2.x が勝手に入りません。ただしこれは、numpy 2 対応版が出そろうまでの据え置きです。対応版が出ているライブラリまで一緒に古いままにすると、別の更新から取り残されます。numpy 2 に上げられない具体的な依存が無いなら、上げる方向を選ぶほうが後を引きません。
切り分け(うまくいかないとき)
numpy.dtype size changed, may indicate binary incompatibilityが出る:同じ原因(C 拡張と numpy の版ズレ)の別の文言です。Expected 96 from C header, got 88 from PyObjectのように、C から見た大きさの食い違いを示します。対処は同じで、ライブラリと numpy の版をそろえます。ただしこの文言は numpy 2.x に上げたときに限らず、C 拡張をビルドした numpy と実行時の numpy がずれていれば出ます(新しい numpy でビルドして古い numpy で実行する逆方向でも起こります)。numpy 2 に上げていないのに出るなら、traceback の numpy 以外の最初のフレームと、その拡張・numpy の版を突き合わせてください。- 1つ上げても、別のライブラリで同じエラーが出る:numpy に依存するライブラリは複数あります。1つずつではなく、numpy を使うパッケージをまとめて numpy 2 対応版に上げてください。
pip listで numpy 系の依存を洗い出せます。 - 上限が無いのに、なぜ pip は止めてくれないのか:ライブラリが numpy に上限(
numpy<2のような指定)を書いていないと、pip から見て numpy 2.x は正当な選択で、衝突とは判定されません。同じ「緩い制約で非互換が入る」形は Python では珍しくなく、実行時に落ちるまで気づきにくい点はFlask が新しい Werkzeug で落ちる例と共通しています。 - numpy 2 に上げたつもりが、まだ 1.x のまま:ほかのパッケージが
numpy<2を要求していて、pip が numpy を上げられていないことがあります。pip show numpyで実際の版を確認し、上限を課している要求元を探します。
検証環境
python:3.11-slim、ネットワーク有り- 再現:
pip install "matplotlib==3.6.3" "numpy==2.0.0"の後、import matplotlib.pyplotがA module that was compiled using NumPy 1.x cannot be run in ...で終了コード 1 - 修正:
pip install "matplotlib==3.9.0" "numpy==2.0.0"(numpy 2 対応版)の後、同じスクリプトが終了コード 0
再現から修正までは errfix の検証ハーネスが機械的に確認しています。numpy 1.x でビルドされた matplotlib を numpy 2.0 と組み合わせると import が落ちること、matplotlib を numpy 2 対応版に上げると同じコードが通ることを、再現→修正→シグネチャ消滅として通しました。pandas==1.5.3 + numpy==2.0.0 では numpy.dtype size changed の文言になることも実測しています。numpy 2.0 が C の互換を変えた点は、numpy の公式リリースノート・移行ガイドに拠ります。