ModuleNotFoundErrorとImportError

Pythonのimportの基礎については、以下のこちらの記事で解説しました。ですが、何故だかimportが上手くいかないそんな事もあると思います。今回はそんな困ったときにどこを確認していくのかを解説します。

そもそもimportとは

ちょっとだけ復習してみましょう。

import sys

この形式の場合、sysの部分はモジュールですね。

from datetime import timedelta

続いてこの形式の場合は、fromに続くのがパッケージで、importに続くのがクラスや関数、そして変数です。

これらの事実はしっかり理解しておく必要があります。

どこでエラーが出ているの?

>>> import sys
>>> from datetime import timedelta
>>> import sys2
Traceback (most recent call last):
File "", line 1, in
ModuleNotFoundError: No module named 'sys2'
>>> from datetime import timedelta2
Traceback (most recent call last):
  File "", line 1, in 
ImportError: cannot import name 'timedelta2' from 'datetime' (/Users/xxx/.pyenv/versions/3.8.1/lib/python3.8/datetime.py)
>>> from datetime2 import timedelta2
Traceback (most recent call last):
  File "", line 1, in 
ModuleNotFoundError: No module named 'datetime2'
>>>

逐一反応が欲しかったので対話モードの例になります。

>>> import sys
>>> from datetime import timedelta

まずは正常にimport出来ている場合は無反応ですね。

>>> import sys2
Traceback (most recent call last):
File "", line 1, in
ModuleNotFoundError: No module named 'sys2'

続いてモジュール名にデタラメなものを指定すると、ModuleNotFoundErrorとなりました。これは想定の範囲内ですね。

>>> from datetime import timedelta2
Traceback (most recent call last):
File "", line 1, in
ImportError: cannot import name 'timedelta2' from 'datetime' (/Users/xxx/.pyenv/versions/3.8.1/lib/python3.8/datetime.py)

次はimportの後をデタラメにしてあるわけないクラス名を指定してみました。その場合は、ImportErrorとなりました。timedeltaがクラス名だと言ってるのはdatetime.pyを見てみたことがあるからです。可能性としては関数や変数という可能性もありますが、マニュアルを見るとこれがクラスだと分かると思います。

>>> from datetime2 import timedelta2
Traceback (most recent call last):
File "", line 1, in
ModuleNotFoundError: No module named 'datetime2'
>>>

最後はfromもimportも指定を間違ってみました。するとfrom側でエラーが検出されていますね。先にfromから見るでしょうから、このこと自体は当たり前かと思います。ですが、なんとエラーはModuleNotFoundErrorです。ここに指定するのってパッケージかもしれないんじゃないの?っていうことで、エラーとしてはモジュールが見つからないと言っていますが、入れたつもりのパッケージが不足している場合でもこの動きになるというのは覚えておきましょう。

ここまででお気づきかも知れませんが、基本的にはおそらく「無いものねだり」がimportエラーの原因です。環境側に問題が無い場合は、ソースコード上のタイプミス、そしてあると思っているクラスが無い、といった事で、逆ソースコード側に問題が無い場合は環境側でセットアップミスとかそういうことになります。

解決策

直接的な解決策というよりも、あれこれやっているうちに、自分のミスに気がつくというのが解決の流れだと思います。

ということで、どこかに思い違いがあるはずなので、それに如何に気がつけるかという話になりますが、1つはマニュアルや解説文に目を通すことです。ただ、おそらくそれは実施済みで、思い込みが邪魔をして大事な事を見落としているか、本当に間違っていないけど環境側に思い違いがあって上手くいっていないかということですが、そういうことを含めて次にあげる内容を点検することで気づくと思いますので順に見てみることをお薦めします。(経験上、だいたい全然関係ないことをチェックしていると思いつくので、関係ないと決めつけずお付き合い下さい)

仮想環境が有効になっていない

このサイトで前提にしている環境なら以下の呪文が必要だったはずです。

source bin/activate

これを忘れていると、インストールしたパッケージの類いは一切見えていないと思いますので当たり前のことですが確認しましょう。あとは、別の環境のactivate しているとか無いですよね…

インストールしているパッケージのリスト

まずは真っ先に見てみたいのは、インストール済みのパッケージリストかなと思います。以下のコマンドを実行すると確認可能です。

pip list

実行してみます。

(LearningPython) MacBookPro:Tips $ pip list
Package Version

autopep8 1.4.4
certifi 2020.6.20
chardet 3.0.4
entrypoints 0.3
et-xmlfile 1.0.1
flake8 3.7.9
idna 2.10
jdcal 1.4.1
mccabe 0.6.1
numpy 1.19.1
openpyxl 3.0.4
pandas 1.1.1
pip 19.3.1
pipenv 2018.11.26
pycodestyle 2.5.0
pyflakes 2.1.1
python-dateutil 2.8.1
pytz 2020.1
requests 2.24.0
setuptools 41.2.0
six 1.15.0
urllib3 1.25.10
virtualenv 16.7.9
virtualenv-clone 0.5.3
xlrd 1.2.0
(LearningPython) MacBookPro:Tips $

インストールしているパッケージとそのバージョンがリストされました。もしここに期待しているパッケージがリストされていないなら、インストールが必要という事かも知れません。(別の環境見ていたとかのミスは事前に回避しておいて下さいね)

見えてるパッケージのリスト

続いて、見えているパッケージをリストしてみましょう。簡単なのはその場で対話モードにして

help("modules")

を実行することです。

(LearningPython) MacBookPro:src $ python
Python 3.8.1 (default, Jan  9 2020, 03:42:29)
[Clang 11.0.0 (clang-1100.0.20.17)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> help("modules")

Please wait a moment while I gather a list of all available modules...

__future__          _warnings           glob                resource
_abc                _weakref            grp                 rlcompleter

>> 途中省略 <<

_tracemalloc        getpass             reprlib             zlib
_uuid               gettext             requests

Enter any module name to get more help.  Or, type "modules spam" to search
for modules whose name or summary contain the string "spam".

>>>

ここにないパッケージを必要としているのであれば、無いものねだりですね。ただ、探す場所順番っていう問題もあります。その場でといったのは、カレントディレクトリも探す対象になるので、場所が違えばってこともあります。

探している場所 sys.path

最後に探しているパスは、sys.pathで確認可能です。

ソースコード

import sys

print("\n".join(sys.path))

printの中の一手間は見やすさのために整形しているだけです。

実行

(LearningPython) MacBookPro:11.import_error $ python import_path.py
/Users/xxx/git/LearningPython/src/Tips/11.import_error
/Users/xxx/.pyenv/versions/3.8.1/lib/python38.zip
/Users/xxx/.pyenv/versions/3.8.1/lib/python3.8
/Users/xxx/.pyenv/versions/3.8.1/lib/python3.8/lib-dynload
/Users/xxx/git/LearningPython/lib/python3.8/site-packages
(LearningPython) MacBookPro:11.import_error $

これをみると分かるとおり、site-packagesは最後です。site-packagesとはpip installしたパッケージがインストールされる先です。

それが最後ということは、自作のモジュールが途中で邪魔をしているというのはあり得ます。システムの標準ライブラリやメジャーなパッケージの名前と衝突していないかのチェックは必要だと思います。

まとめ

このほかにも関連する話題として、環境変数を使って探す場所を追加してみたり、ソースコード上で追加するといったことも可能なのですが、ちょっと話題からそれてしまうので割愛しました。もしそういったことをしているのが原因だとしても、上記の確認の中で結果として登場するので確認する箇所には影響していないと思います。

プログラミング中は、つまらない間違い探しが一番時間をとってしまう作業だと思います。この説明が少しでも解決の近道になっていると嬉しいですし、ちょっと調べるの止めてみるというのも解決の近道だったりするので、それも試してみて下さい。