11
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PyInstallerのImportErrorとModuleNotFoundErrorで躓いた話

Last updated at Posted at 2022-03-13

こいついつも躓いてんな
PyInstallerでPythonスクリプトをexe化した際エラーが出ててんやわんやしていたのですが、ようやく解決できたのでまとめておきます。

また、仮想環境生成のためにPyCharmを使用しています。

更新履歴

2022/03/13 : 初版

環境

  • WIndows10
  • Python3.10
  • PyCharm Community Edition 2021.2.2

1. exe化の際指定したファイル内でImportError

こういうエラー。
exe化の際指定したのがmyprojectパッケージ内のmain.pyファイルで、その内部で「相対インポート使ってんじゃねーよ!」と怒られています。

ImportError
Traceback (most recent call last):
  File "myproject\main.py", line 4, in <module>
ImportError: attempted relative import with no known parent package

1.1. 状況の再現

1.1.1. プロジェクト構成

プロジェクト全体を格納するフォルダ(Root)

プロジェクト構成
myproject

その下にソースコードを格納する、プロジェクト名と同名のフォルダ

プロジェクト構成
myproject
    └ myproject

パッケージ化のため__init__.pyを追加します。
メインとなるモジュール(main.py)と、その中でインポートするモジュール(sub.py)を用意します。

プロジェクト構成
myproject
    └ myproject
        ├ __init__.py
        ├ main.py
        └ sub.py

1.1.2. 各ファイルの内容

mainsubをインポートします。
パッケージ内なので相対インポート!ということで、from . import subと書いてみます。

参考の為、__name____package__を出力しておきます。
コマンドプロンプトが一瞬で消えるのを回避するため、subprocessPAUSEします。

main.py
print(__name__, __package__)

import subprocess
subprocess.Popen('PAUSE', shell=True)

from . import sub
sub.py
print(__name__, __package__)

__init__.pyは空ファイルです。

1.1.3. exe化実行

PyCharmのTerminalから次のコマンドでexe化を実行します。

exe化実行
pyinstaller --onefile main.py

exe化......できてない!

実行結果
55 INFO: PyInstaller: 4.10
55 INFO: Python: 3.10.0
60 INFO: Platform: Windows-10-10.0.19043-SP0
61 INFO: wrote C:\work\myproject\main.spec
63 INFO: UPX is not available.
script 'C:\work\myproject\main.py' not found

これの原因はすぐにわかりました。
カレントディレクトリがプロジェクトのRootフォルダになっていました。
ファイル名をmain.pyとしか書かなかったので、カレントディレクトリ内のmain.pyファイルを探してしまったんですね。
ということで、ソースコードのフォルダへ移動します。

フォルダ移動
# これでカレントディレクトリがmyproject\myprojectになる
cd myproject

もう一度exe化を実行します。

exe化実行
pyinstaller --onefile main.py

今度は成功したようです。

実行結果
......
5945 INFO: Updating resource type 24 name 1 language 0
5946 INFO: Appending PKG archive to EXE
7455 INFO: Building EXE from EXE-00.toc completed successfully.

1.1.4. exeファイルを実行するとImportError

myproject\myproject\dist\main.exeを実行するとImportErrorになりました。

実行結果
__main__ None
Traceback (most recent call last):
......
ImportError: attempted relative import with no known parent package
......

1.2. 原因

PyInstallerの仕様として、exe化を指示したファイルはパッケージに属さないものとして扱われます。
そのため、パッケージに属さないmain.py内で相対インポートを使おうにも、どこからの相対なのかという基準がわからずエラーとなったのです。
setup.pyentry_points指定すればパッケージ構造も実行ファイルも情報渡せるから何とかならん?」とか思ったりもしたのですが、そういう訳にもいかない様子。
【参考】Pyinstall a package with __main__.py - GitHub
「数行修正すればいいだけのことにそんな労力かけてられないよ!」みたいなことが書かれています。

1.3. 対策

exe化を指示するファイル内で相対インポートは使用しないことが大切です。

main.py
print(__name__, __package__)

import subprocess
subprocess.Popen('PAUSE', shell=True)

from . import sub

from . import subで相対インポートを使用していますね。
これを絶対インポートに書き直せば良いのですが、次のような書き方をしないようにしましょう。

間違った書き方
import sub

これでexe化すると「subなんてモジュールはねえよ!(ModuleNotFoundError)」って怒られます。
なぜなら、これは暗黙的な相対インポートであって絶対インポートではないからです。
これは次節で扱います。
【参考】Pythonのインポートを誤解して躓いた話 - Qiita

自作モジュールに関しては、必ずパッケージ名を指定します。

正しい書き方
from myproject import sub

なお、exe化を指示するファイル以外では相対インポートの使用はOKです。

1.4 まとめ

  • exe化の際指定するファイル内で相対インポートは使用しない
  • from myproject import subのようにパッケージ名を指定した絶対インポートを使用する

2. 自作モジュールに対してModuleNotFoundError

こういうエラー。
exe化で指定したmain.pyと同じフォルダにsub.pyがあるはずだけど、「mainなんてモジュールはねーよ!」と怒られています。

Traceback (most recent call last):
  File "myproject\main.py", line 3, in <module>
ModuleNotFoundError: No module named 'sub'

2.1. 状況の再現

2.1.1. プロジェクト構成

プロジェクト全体を格納するフォルダ(Root)

プロジェクト構成
myproject

その下にソースコードを格納する、プロジェクト名と同名のフォルダ

プロジェクト構成
myproject
    └ myproject

パッケージ化のため__init__.pyを追加します。
メインとなるモジュール(main.py)と、その中でインポートするモジュール(sub.py)を用意します。

プロジェクト構成
myproject
    └ myproject
        ├ __init__.py
        ├ main.py
        └ sub.py

2.1.2. 各ファイルの内容

mainsubをインポートします。
このとき「mainを実行するんだからmyproject\myprojectにPython検索パスが通るはずで、だったらこう書けるだろう」と考え、import subと書きました。

main.py
print(__name__, __package__)

import subprocess
subprocess.Popen('PAUSE', shell=True)

import sub
sub.py
print(__name__, __package__)

__init__.pyは空ファイルです。

2.1.3. exe化実行

PyCharmのTerminalから次のコマンドでexe化を実行します。

exe化実行
cd myproject
pyinstaller --onefile main.py

2.1.4. exeファイルを実行するとModuleNotFoundError

myproject\myproject\dist\main.exeを実行するとModuleNotFoundErrorになりました。

実行結果
__main__ None
Traceback (most recent call last):
  File "myproject\main.py", line 4, in <module>
ModuleNotFoundError: No module named 'sub'

2.2. 原因

pyinstallerを介した時点でmainは実行ファイルではありません(=Python検索パスは通りません)。
なので、import subという書き方は絶対インポートではなく暗黙的な相対インポートという扱いになります。
そしてPython3では暗黙的な相対インポートは禁止されてますから、エラーとなります。

2.3. 対策

絶対インポートまたは明示的な相対インポートを使用します。
ただし、exe化を指定するファイルに関しては前述の通り、絶対インポートを使用してください。

main.py
print(__name__, __package__)

import subprocess
subprocess.Popen('PAUSE', shell=True)

from myproject import sub

2.4. まとめ

  • 暗黙的な相対インポートになっていないか確認
  • exe化を指定するファイルでは絶対インポート
  • それ以外では絶対インポートか明示的な相対インポート

3. 仮想環境上のパッケージに対してModuleNotFoundError

こういうエラー。
仮想環境上でnumpyをインストールしたけど、「numpyなんてモジュールはねーよ!」と怒られています。

Traceback (most recent call last):
  File "myproject\__main__.py", line 3, in <module>
ModuleNotFoundError: No module named 'numpy'

3.1. 状況の再現

3.1.1. プロジェクト構成

プロジェクト全体を格納するフォルダ(Root)

プロジェクト構成
myproject

その下にソースコードを格納する、プロジェクト名と同名のフォルダ

プロジェクト構成
myproject
    └ myproject

パッケージ化のため__init__.pyを追加します。
メインとなるモジュール(main.py)を用意します。

プロジェクト構成
myproject
    └ myproject
        ├ __init__.py
        └ main.py

3.1.2. 各ファイルの内容

main.pyで仮想環境上にしかインストールしていないパッケージをインポートします。
今回はnumpyで試してみます。

main.py
print(__name__, __package__)
import numpy

3.1.3. exe化実行

コマンドプロンプトから次のコマンドでexe化を実行します。

exe化実行
cd myproject
pyinstaller --onefile main.py

3.1.4. exeファイルを実行するとModuleNotFoundError

myproject\myproject\dist\main.exeを実行するとModuleNotFoundErrorになりました。

実行結果
__main__ None
Traceback (most recent call last):
  File "myproject\main.py", line 4, in <module>
ModuleNotFoundError: No module named 'numpy'

3.2. 原因

仮想環境上のプロジェクトに対してグローバルのpyinstallerを実行したためです。
このとき仮想環境中のパッケージはグローバルのPython検索パス上に存在しないため、読み込まれないのです。
なお、今回pyinstallerをコマンドプロンプトから実行しましたが、同様の問題はPyCharmのTerminalから実行したときでも発生する可能性があります。
それは、「PyCharmのTerminalで仮想環境が起動していないとき」です。
詳しくはこちらをご覧ください。
【参考】PyCharmのTerminalで仮想環境が自動起動せずに躓いた話 - Qiita

3.3. 対策

方法は3つありますので、順に紹介します。
ただその前に注意すべきなのが、pyinstallerPyInstaller別物だということです。

  • pyinstaller
    Scripts\pyinstaller.exeを示しています。
    exeファイルです。
    なので、python -m pyinstaller --onefile main.pyとか書くと当然エラーです。

  • PyInstaller
    Lib\site-packages\PyInstallerを示しています。
    Pythonパッケージです。
    なので、PyInstaller --onefile main.pyとか書くと当然エラー......と思いきや、PowerShellやCMDではコマンド名の大文字小文字が区別されないので実行できてしまいます。
    その場合、実行されるのはpyinstaller.exeの方です。
    python -m pyinstallerpyinstallerはコマンド名ではないので、こちらはエラーです。

3.3.1. 仮想環境上のpyinstaller.exeを使用

グローバルにpyinstallerをインストールしている場合、予めアンインストールしておいてください。

PyCharmのTerminalを開きます。
このとき仮想環境が起動していることを確認してください。
次のコマンドでpyinstallerをインストールします。

pyinstallerインストール
python -m pip install pyinstaller

インストールが終わったら、続けて次のコマンドを試してみてください。

pyinstaller.exeの場所(TerminalがPowerShellの場合)
Get-Command pyinstaller
pyinstaller.exeの場所(TerminalがCMDの場合)
where pyinstaller

仮想環境中のpyinstaller.exeを示していれば成功です。

実行結果
C:\work\myproject\venv\Scripts\pyinstaller.exe

exe化する際は、仮想環境起動中のPyCharmのTerminalで次のようにして実行します。

pyinstaller実行
cd myproject
pyinstaller --onefile main.py

3.3.2. 仮想環境上のPyInstallerパッケージを使用

グローバルにpyinstallerをインストールしている場合、予めアンインストールしておいてください。

PyCharmのTerminalを開きます。
このとき仮想環境が起動していることを確認してください。
次のコマンドでpyinstallerをインストールします。

pyinstallerインストール
python -m pip install pyinstaller

インストールが終わったら、続けて次のコマンドを試してみてください。

PyInstallerの場所
 python -c "import PyInstaller; print(PyInstaller.__file__)

仮想環境中のPyInstallerパッケージを示していれば成功です。

実行結果
C:\work\myproject\venv\lib\siteーpackages\PyInstaller\__init__.py

exe化する際は、仮想環境起動中のPyCharmのTerminalで次のようにして実行します。

PyInstaller実行
cd myproject
python -m PyInstaller --onefile main.py

3.3.3. グローバルのPyInstallerパッケージを使用

この方法を使用する場合、PyCharmのPython Interpreter生成時Inherit global site-packagesにチェックを入れている必要があります。

コマンドプロンプトを開き、グローバルにpyinstallerをインストールします。

pyinstallerインストール
python -m pip install pyinstaller

exe化する際は、仮想環境起動中のPyCharmのTerminalで次のようにして実行します。

PyInstaller実行
cd myproject
python -m PyInstaller --onefile main.py

仮想環境上にはpyinstallerをインストールしていませんが、グローバルにインストールされていれば使用可能です。
このとき実行しているpython.exeが仮想環境上のものになることがポイントで、この場合仮想環境上のsite-packagesもPython検索パスに追加されます。

3.3.4. 補足

pyinstaller.exepython.exe&PyInstallerのパターンは上記以外にもありますが、全てダメです。
pyinstaller.exeまたはpython.exeがグローバルのものである場合、仮想環境上のsite-packagesがPython検索パスに追加されず、exe実行時にエラーとなります。

3.4. まとめ

うまくいくのは次のパターン

  • 仮想環境上のpyinstaller.exeを使用する
  • 仮想環境上のpython.exePyInstallerパッケージを使用する
  • Python Interpreter生成時にInherit global site-packagesにチェックを入れた上で、仮想環境上のpython.exeとグローバルのPyInstallerパッケージを使用する

4. exe化の処理中にIndexError

おまけ

4.1. 原因

Python3.10のバグのようです。
【参考】【Python3.10】pyinstallerでindex out of rangeが出る

4.2. 対策

Lib\dis.py内の_unpack_opargs関数を探します。
次のような記述があるので、extended_arg = 0を追加します。

Lib\dis.py\_unpack_opargs
else:
    arg = None
    extended_arg = 0 
yield (i, op, arg)

【参考】IndexError: tuple index out of range when I try to create an executable from a python script using auto-py-to-exe - stackoverflow

5. まとめ

pyinstallerのエラーにはいろいろな原因があります。
他にも何かあれば追加していきたいと思います。

11
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?