見出し画像

numpyインストールで四苦八苦

Pythonでは超有名なパッケージnumpyを試してみようとpipを使ってインストールし、テスト用のスクリプトimportをしたらエラーになりました。

Pythonはパッケージ管理が簡単・素早く・楽チンだと聞いていたので、こういうエラーは私のようなPython初心者殺しです。

C:\temp\python>py -3.8 test.py
Traceback (most recent call last):
 File "test.py", line 1, in <module>
   import numpy
 File "C:\Users\Uname\AppData\Local\Programs\Python\Python38\lib\site-packages\numpy\__init__.py", line 140, in <module>
   from . import _distributor_init
 File "C:\Users\Uname\AppData\Local\Programs\Python\Python38\lib\site-packages\numpy\_distributor_init.py", line 26, in <module>
   WinDLL(os.path.abspath(filename))
 File "C:\Users\Uname\AppData\Local\Programs\Python\Python38\lib\ctypes\__init__.py", line 373, in __init__
   self._handle = _dlopen(self._name, mode)
OSError: [WinError 193] %1 は有効な Win32 アプリケーションではありません。

このエラーはアーキテクチャが異なる場合に発生するみたいです。たとえば、32ビットのPythonに64ビットのパッケージをインストールしようとした場合などです。

私の場合は64ビットのPythonをインストールしたのに、32ビットのパッケージをインストールしようとして発生しているエラーでした。

最終的にはnumpyをインストールすることができたのですが、インストールするまでに試した手順を残すことにしました。今後も同じような目にあったときの備忘録としても役立つ可能性があるからです。

再インストールしてみる

一度、pip uninstall numpyを実行し、再度インストールしました。確認してみると、たしかにwin32.whlがインストールされていたことがキャッシュの内容からわかります。


Windowsの64ビット版はファイル名の末尾がwin_amd64.whlになります。

C:\temp\python>py -3.8 -m pip install numpy
Collecting numpy
 Using cached numpy-1.18.3-cp38-cp38-win32.whl (10.8 MB)
Installing collected packages: numpy
Successfully installed numpy-1.18.3

uninstallとinstallという2段階の手続きを踏むのが面倒な人はpip install numpy --forceと強制インストールするオプションスイッチをつけると一発で再インストールできます。

python.exeの確認

誤ってpython.exeの32ビット版をインストールしていないかどうかを確認しました。起動メッセージからもパッケージplatformからも、64ビット版をインストールしていることが確認できました。

C:\temp\python>py -3.8
Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import platform
>>> platform.architecture()
('64bit', 'WindowsPE')
>>> exit()

pipのバージョン確認

pipが悪さをしているのか、バージョンを確認します。

C:\temp\python>py -3.8 -m pip -V
pip 20.1 from C:\Users\Uname\AppData\Local\Programs\Python\Python38\lib\site-packages\pip (python 3.8)

念のため、最新版へアップグレードされていることを確認します。already up-to-dateとなっており、最新版になっていることが確認できました(2020年4月30日時点)。

C:\temp\python>py -3.8 -m pip install --upgrade pip
Requirement already up-to-date: pip in c:\users\Uname\appdata\local\programs\python\python38\lib\site-packages (20.1)

Whlファイルからインストール

Wheel形式のファイルを次のサイトからダウンロードし、直接的にインストールしてみます。インストールされているpython.exeのバージョンが3.8、64ビット版なので、cp38-cp38-win_amd64.whlになっているものをダウンロードします。

Unofficial Windows Binaries for Python Extension Packages
https://www.lfd.uci.edu/~gohlke/pythonlibs/#numpy

ダウンロードしたら、Wheel形式のファイルがあるフォルダへ直接移動し、pip installコマンドを叩きます。

C:\Downloads>py -3.8 -m pip install hogehoge4.3-cp38-cp38-win_amd64.whl
ERROR: hogehoge4.3-cp38-cp38-win_amd64.whl is not a supported wheel on this platform.

not a supportedというエラーになってしまいました。

py launcherを確認する

「もしかしたらpy.exeが64ビットではないのだろうか?」と思ったので、dumpbinコマンドを使って実行ファイルのビット数を確認しました。

dumpbinコマンドはVisual Studioに付属しているツールです。確認してみると32ビットになっていました。

C:\Windows>dumpbin /headers py.exe | findstr machine
            14C machine (x86)
                  32 bit word machine

しかし、ラウンチャはpython.exeを起動しているだけなのでpy.exeそのものには問題はないような気がします。

何かしらのときのために、ひとつの情報として残しておきます。

python.exeからpipを叩く

次は直接python.exeの存在するフォルダへ移動し、pipインストールを試みます。

pip uninstall numpyを実行して、再インストールしましたが、やはり32ビットを見に行ってしまいます。

C:\Users\Uname\AppData\Local\Programs\Python\Python38>python -m pip install numpy
Collecting numpy
 Using cached numpy-1.18.3-cp38-cp38-win32.whl (10.8 MB)
Installing collected packages: numpy
Successfully installed numpy-1.18.3

Wheel形式ファイルを直接見に行ってもやはり結果は同じです。

C:\Downloads>\Users\Uname\AppData\Local\Programs\Python\Python38\python.exe -m pip install "numpy-1.18.3+mkl-cp38-none-win_amd64.whl"
ERROR: numpy-1.18.3+mkl-cp38-cp38-win_amd64.whl is not a supported wheel on this platform.

ファイルをリネームする

次のサイトを見ると、ファイル名からバージョン判定をしているとの記述があったので、cp38-cp38の部分をcp38-noneへリネームして試してみました。

俺言語。
http://oregengo.hatenablog.com/entry/2016/12/20/175835

それでも結果は同じでした。

C:\Downloads>ren "numpy-1.18.3+mkl-cp38-cp38-win_amd64.whl" "numpy-1.18.3+mkl-cp38-none-win_amd64.whl"
C:\Downloads>py -3.8 -m pip install "numpy-1.18.3+mkl-cp38-none-win_amd64.whl"
ERROR: numpy-1.18.3+mkl-cp38-none-win_amd64.whl is not a supported wheel on this platform.

もう、やぶれかぶれです。ファイル名をcp38-cp38-win32.whlにを変えてしまいましょう。環境が壊れたら32ビット版pythonをインストールして凌ぎます。

C:\Downloads>ren "numpy-1.18.3+mkl-cp38-none-win_amd64.whl" "numpy-1.18.3+mkl-cp38-cp38-win32.whl"
C:\Downloads>py -3.8 -m pip install "numpy-1.18.3+mkl-cp38-cp38-win32.whl"
Processing c:\downloads\numpy-1.18.3+mkl-cp38-cp38-win32.whl
Installing collected packages: numpy
 Attempting uninstall: numpy
   Found existing installation: numpy 1.18.3
   Uninstalling numpy-1.18.3:
     Successfully uninstalled numpy-1.18.3
 WARNING: The script f2py.exe is installed in 'C:\Users\Uname\AppData\Local\Programs\Python\Python38\Scripts' which is not on PATH.
 Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
Successfully installed numpy-1.18.3+mkl

なんと、インストールできてしまいました。

numpyサンプルを試す

次のサイトにあるnumpyのサンプルスクリプトを使ってnumpyが動くかどうかを確認してみます。

【Python】Numpyのサンプル
https://qiita.com/yut-nagase/items/94b4798eb88de16b36be

問題なく動いています。

C:\temp\python>py -3.8 test.py
[100 200 300 400 500]
[0.1 0.2 0.3 0.4 0.5]
[100.1 200.2 300.3 400.4 500.5]

numpyをインストールする目的は達成できましたが、いまいち釈然としません。なんでWin32用のモジュールを見に行ってしまうのでしょうか?

pipモジュールを調べる

ここからは私の趣味です。

そもそもの原因を知りたいので、仕方なくpipモジュールをコードリーディングしてみることにしました。

結果から言うと、小一時間程度の調査ではわかりませんでした。ですので、お時間のある方は読み物として楽しんでください。

いくつかのファイルを眺めてみたらsysモジュールのplatformを参照しているらしき箇所を見つけました。まずはここを切り口に調べることにしました。

--- pip\_internal\utils\filesystem.py ---
29: def check_path_owner(path):
30:     # type: (str) -> bool
31:     # If we don't have a way to check the effective uid of this process, then
32:     # we'll just assume that we own the directory.
33:     if sys.platform == "win32" or not hasattr(os, "geteuid"):
34:         return True

実際、pythonを起動してsys.platformの値を表示させると"win32"となっていました。おそらくpipインストール時にはsys.platformを参照しているのだとアタリをつけます。

C:\temp\Python>py -3.8
Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print(sys.platform)
win32
>>>

Pythonのソースコードを探す

sysモジュールはパッケージ化されています。つまりビルド済みなのでソースコードを遡る必要があるということです。Pythonのソースコードを見れば、何かしらのヒントはあるのでしょうか?

ソースコードをダウンロードし、ファイル名からそれらしき名前のものを探してみると、sysmodule.cというソースコードを見つけることができました。

--- Python-3.8.2\Python\sysmodule.c ---
2669: SET_SYS_FROM_STRING("platform",
2670: PyUnicode_FromString(Py_GetPlatform()));

Pythonのパッケージ開発をしたことはありませんが、文字列"platform"に紐づけているのだと思います。試しにpython.exeを起動し、この行の前後にあるソースコードから文字列を拾って「sys.ほにゃらら」と打ち込んでみたら、それらのモジュールが呼ばれたので間違いないと思います。

マクロSET_SYS_FROM_STRINGを使って、文字列と実行体である関数を紐づけていることが確認できたので、今度は文字列"platform"に紐づけられている関数Py_GetPlatformを探します。

ずばり、getplatform.cというソースコードがありました。

--- Python-3.8.2\Python\getplatform.c ---
2: #include "Python.h"
3: 
4: #ifndef PLATFORM
5: #define PLATFORM "unknown"
6: #endif
7: 
8: const char *
9: Py_GetPlatform(void)
10: {
11:     return PLATFORM;
12: }

ヘッダPython.hをインクルードしているので、ソースコードを辿ります。しかし、Python.hにはPLATFORMの定義はありませんでした。そこでPython.hからインクルードしているヘッダを辿っていきます。

マクロPLATFORMを定義しているのはヘッダpyconfig.hです。該当する箇所を調べてみるとMS_WIN64でもマクロPLATFORMを"win32"で定義しています。きっとここが悪さをしているのかもしれません。

--- Python-3.8.2\PC\pyconfig.h ---
287: #if defined(MS_WIN64)
288: /* maintain "win32" sys.platform for backward compatibility of Python code,
289:   the Win64 API should be close enough to the Win32 API to make this
290:   preferable */
291: #       define PLATFORM "win32"
292: #       define SIZEOF_VOID_P 8
293: #       define SIZEOF_TIME_T 8
294: #       define SIZEOF_OFF_T 4
295: #       define SIZEOF_FPOS_T 8
296: #       define SIZEOF_HKEY 8
297: #       define SIZEOF_SIZE_T 8
298: /* configure.ac defines HAVE_LARGEFILE_SUPPORT iff
299:   sizeof(off_t) > sizeof(long), and sizeof(PY_LONG_LONG) >= sizeof(off_t).
300:   On Win64 the second condition is not true, but if fpos_t replaces off_t
301:   then this is true. The uses of HAVE_LARGEFILE_SUPPORT imply that Win64
302:   should define this. */
303: #       define HAVE_LARGEFILE_SUPPORT
304: #elif defined(MS_WIN32)
305: #       define PLATFORM "win32"
306: #       define HAVE_LARGEFILE_SUPPORT
307: #       define SIZEOF_VOID_P 4
308: #       define SIZEOF_OFF_T 4
309: #       define SIZEOF_FPOS_T 8
310: #       define SIZEOF_HKEY 4
311: #       define SIZEOF_SIZE_T 4
312:        /* MS VS2005 changes time_t to a 64-bit type on all platforms */
313: #       if defined(_MSC_VER) && _MSC_VER >= 1400
314: #       define SIZEOF_TIME_T 8
315: #       else
316: #       define SIZEOF_TIME_T 4
317: #       endif
318: #endif

自分でPythonをソースコードからビルドして試してみたい気もしますが、私は面倒くさがりなのでpython38.dllのバイナリを直接いじって、sys.platformの値を"win64"へ変更しました。

python.exeがこの改変されたdllを読み込めば、sys.platformを参照しているはずのpipがcp38-cp38-win64.whlというファイルを見に行くはずです。

結果はダメでした。やはりcp38-cp38-win32.whlを見に行ってしまいます。

python38.dll_変更

再びpipモジュールを見る

アテが外れたので再びpip側のソースコードに戻り、参照すべきパッケージ名を生成する箇所を探してみました。

するとsysconfig.pyというものを見つけることができました。

面白いことにバージョン文字列から当該プラットフォームを抽出しており、プラットフォームの抽出に失敗した場合にsys.platformを参照するという仕組みになっています。

--- site-packages\pip\_vendor\distlib\_backport\sysconfig.py ---
626: if os.name == 'nt':
627:     # sniff sys.version for architecture.
628:     prefix = " bit ("
629:     i = sys.version.find(prefix)
630:     if i == -1:
631:         return sys.platform
632:     j = sys.version.find(")", i)
633:     look = sys.version[i+len(prefix):j].lower()
634:     if look == 'amd64':
635:         return 'win-amd64'
636:     if look == 'itanium':
637:         return 'win-ia64'
638:     return sys.platform

pythonを起動して、コードを直接打ち込んでみても文字列"win-amd64"が取れます。ソースコードの動作には問題なさそうに見えますが、どうしてもcp38-cp38-win32.whlを見に行ってしまうようです。

どこを参照しているのかがわからなくなってきたので今回の調査はここまでです。また何かわかったら追記します。

とりあえずの対処方法としては、Wheel形式のファイル名を自動読み込みしているパッケージ名に強制リネームするのがよいということがわかりました。

この記事が気に入ったらサポートをしてみませんか?