1. ホーム

Pyinstallerパッケージリリース体験談まとめ

2022-03-16 21:42:33

Pyinstallerを使用してPythonプロジェクトをパッケージ化することは、多くの落とし穴を含んでいます。この記事では、実践から得たPyinstallerパッケージ化の経験をまとめています。この記事の例は、Python 3.6 コード、Pyinstaller 3.4 で、Windows 環境で 64 ビット版と 32 ビット版としてパッケージングされています。

内容紹介

Pyinstallerの基本的な使い方

Pythonプロジェクトのパッケージング方法

1.スペックファイルの生成

2. スペックファイルの設定

3. specを指定してパッケージングコマンドを実行

Visual C++ ランタイム .dll が含まれます。

Pythonモジュールのパッケージングに関する問題

パッケージングパスの凍結

その他の問題


Pyinstallerの基本的な使い方

Pythonプロジェクトのパッケージング方法

1.スペックファイルの生成

2. スペックファイルの設定

3. specを指定してパッケージングコマンドを実行

Pythonモジュールのパッケージングに関する問題

パッケージングパスの凍結

その他の問題


Pyinstallerの基本的な使い方

Pyinstaller は、簡単なコマンドで Python コードをパッケージ化するために使用できます。基本的なコマンドは次のとおりです。

pyinstaller -option xxx.py

オプションの詳細については、公式のヘルプドキュメントを参照してください。 https://pyinstaller.readthedocs.io/en/stable/usage.html

ここでは、使用するオプションのみを説明します。-dは実行ファイルとそれに付随するダイナミックリンクライブラリ、リソースファイルなどを含むファイルディレクトリを生成します、-fは実行ファイルのみを生成します。

<テーブル -D, --onedir 実行ファイルを含むワンフォルダーバンドルを作成します(デフォルト)。 -F, --onefile 1ファイルのバンドル実行ファイルを作成します。

パッケージング結果が大きいプロジェクトでは、-d生成ディレクトリは単一の実行ファイルによるパッケージング方法よりも実行速度が速いですが、より多くのファイルが含まれます。この例では、-D方式が選択されています。

Pythonプロジェクトにおけるパッケージング方法

複数のファイルとディレクトリからなるPythonプロジェクトを例にとると、プロジェクトファイルには、1.Pythonソースファイル、2.アイコンリソースファイル、3.その他のリソースファイル、が含まれています。

Pythonのソースファイルはbin, libapp, libmodels, libviewsに、アイコンはlibiconディレクトリに、その他のリソースファイルはdataディレクトリにあり、テキストファイルやビデオファイルなどが含まれています。

1.スペックファイルの生成

カスタム設定をパッケージングするためには、まずパッケージング設定ファイル .spec ファイルを記述する必要があります。pyinstaller -d xxx.py を使用した場合、デフォルトのパッケージング設定用の xxx.spec ファイルが生成されます。カスタムパッケージングは、specスクリプトを設定し、pyinstaller -d xxx.specを実行することで行われます。

generate spec file コマンドでコードのメインプログラムファイル用にパッケージされた spec ファイルを生成します。

 pyi-makespec -w xxx.py

生成されたspecファイルを開き、そのデフォルトのスクリプトを修正して、カスタムパッケージングに必要な設定を完了します。specファイルは、次の例に示すようなデフォルトの構造を持つPythonスクリプトです。

# -*- mode: python -*-

block_cipher = None


a = Analysis(['fastplot.py'],
             pathex=['D:\\install_test\\\DAGUI-0.1\\bin'],
             binaries=[],
             datas=[],
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          exclude_binaries=True,
          name='fastplot',
          debug=False,
          strip=False,
          upx=True,
          console=False )
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               name='fastplot')

specファイルには、主に4つのクラスが含まれています。Analysis、PYZ、EXE、COLLECTの4つの主要なクラスがあります。

  • 解析はpyファイルを入力として受け取り、依存するモジュールがあるかどうかを解析し、対応する情報を生成します

  • PYZ は、プログラムの実行に必要なすべての依存関係を含む .pyz アーカイブです。

  • 上記2項目を元にEXEを生成する

  • COLLECTは他の部分の出力フォルダを生成し、COLLECTがなくても

2. スペックファイルの設定

サンプルPythonプロジェクトのspecファイルの構成を最初に示します。

# -*- mode: python -*-
import sys
import os.path as osp
sys.setrecursionlimit(5000)

block_cipher = None


SETUP_DIR = 'D:\\install_test\\\\FASTPLOT\\'

a = Analysis(['fastplot.py',
              'frozen_dir.py',
              'D:\\install_test\\\FASTPLOT\\\lib\\\\app\\\start.py',
             'D:\\install_test\\\FASTPLOT\\\lib\\\models\\\\analysis_model.py',
             'D:\\install_test\\\FASTPLOT\\\lib\\\\models\\\\datafile_model.py',
             'D:\\install_test\\\FASTPLOT\\lib\\\\models\\\\data_model.py',
             'D:\\install_test\\\FASTPLOT\\lib\\\\models\\\\figure_model.py',
             'D:\\install_test\\\FASTPLOT\\\lib\\\models\\\\time_model.py',
             'D:\\install_test\\\FASTPLOT\\\lib\\\\models\\\\mathematics_model.py',
             'D:\\install_test\\\FASTPLOT\\\lib\\\\views\\\\constant.py',
             'D:\\install_test\\\FASTPLOT\\\lib\\\\views\\\\custom_dialog.py',
             'D:\\install_test\\\FASTPLOT\\lib\\\\views\\\\data_dict_window.py',
             'D:\\install_test\\\FASTPLOT\\lib\\\\views\\\\data_process_window.py',
             'D:\\install_test\\\FASTPLOT\\lib\\\\views\\\\data_sift_window.py',
             'D:\\install_test\\\FASTPLOT\\lib\\\\views\\\\mathematics_window.py',
             'D:\\install_test\\\FASTPLOT\\\lib\\\\views\\\\para_temp_window.py',
             'D:\\install_test\\\FASTPLOT\\\lib\\\\views\\\\mainwindow.py',
             'D:\\install_test\\\FASTPLOT\\\lib\\\\views\\\paralist_window.py',
             'D:\\install_test\\\FASTPLOT\\\lib\\\\views\\\\plot_window.py'],
             pathex=['D:\\install_test\\\\FASTPLOT'],
             binaries=[],
             datas=[(SETUP_DIR+'lib\\icon','lib\\icon'),(SETUP_DIR+'data','data')],
             hiddenimports=['pandas','pandas._libs','pandas._libs.tslibs.np_datetime','pandas._libs.tslibs.timedeltas',
             'pandas._libs.tslibs.nattype', 'pandas._libs.skiplist', 'scipy._lib', 'scipy._lib.messagestream'],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
       

a) pyファイルのパッケージング設定

マルチディレクトリ、マルチファイルの python プロジェクトでは、関連するすべての py ファイルを Analysis クラスに入力する必要があり、pathex でパッケージのホームディレクトリを定義します。spec スクリプトは、上記のように、プロジェクト内のすべての py ファイルのパスをリストとして Analysis に書き込みます。ここでは説明のために絶対パスと相対パスを混ぜて使用しています。

b) リソースファイルのパッケージング設定

リソースファイルには、アイコンファイルやテキストファイルなど、パッケージ化されたPythonプロジェクトで使用される関連ファイルが含まれています。このようなリソースファイルをパッケージングするには、Analysis datasを設定する必要があります。例に示すように、 datasはタプル: datas=[(SETUP_DIR+'libttpicon','libttpicon'),(SETUP_DIR+'data','data')] を受け取ります。このタプルの構成は、(元のプロジェクトのリソースファイルのパス、パッキング後のパス) であり、この例の (SETUP_DIR+'lib³³','lib³³') は D:\install_test↘からアイコンファイルをパッキングして、パッキング結果パスの lib³³³³ に置くことを意味している。

c) 非表示のインポート設定

pyinstaller がパッケージ化するとき、パッケージ化された python ファイルを解析して、py ソースファイルの依存モジュールを自動的に見つけます。しかし、pyinstaller はモジュールを解析するときに、いくつかのモジュール(解析フェーズでは見えない)を見逃してしまい、パッケージング後のプログラム実行時に No Module named xxx のような結果になることがあります。そこで、例に示すように Analysis の下にある hiddenimports に見逃しているモジュールを追加する必要があります。

d) 再帰深度の設定

インポート用にいくつかのモジュールをパッケージングするとき、しばしば "RecursionError: maximum recursion depth exceeded" というエラーが発生します。これはおそらく、パッケージング時に Python があらかじめ設定した再帰の深さを超えて、多くの再帰が発生しているためです。そこで、spec ファイルに recursion depth の設定を追加し、パッキングを継続するのに十分な大きさの値に設定する必要があります。

import sys
sys.setrecursionlimit(5000)


e) 不要なmodulesimportを削除する

時には、pyinstallerが使用していないモジュールをパッケージ化しないようにする必要があります。これは、次のようにexcludes=[]にこのモジュールを追加することで実現できます。

excludes=['zmq']

3. specでpackageコマンドを実行する

pyinstaller -D xxx.spec

パッケージはbuildとdistという2つのファイルディレクトリを生成します。buildはパッケージ終了後に削除できる一時的なファイルディレクトリで、distはパッケージの結果を保持し、プログラムが実行される実行ファイルとその他の関連ファイルはこのディレクトリに入ります。

Visual C++ ランタイム .dll には以下のものが含まれます。

Python>=3.5でWindows<10のリリースで使用するために、Pyinstallerパッケージのプログラムは、Visual C++ランタイム.dllが含まれていないように見えるかもしれませんが、Python>=3.5では、ユニバーサルCRT、これらのランタイムWin10自体またはアップデートパッケージのWin7にWin8.1バージョンの使用ですが、プログラムは必ずしもシステムを使用してパッケージされていませんインストールされています。だから、あなたが参照する必要があります ユニバーサルCRT を適用することで、この問題を解決するためのアドバイスを得ることができます。

  1. ビルドオン  Windows 7  で、動作報告がされています。

  2. アプリケーションのインストーラに、VCRedistパッケージ(再配布可能なパッケージファイル)のいずれかをインクルードする。これはMicrosoftの推奨する方法です。上記のリンクの2番と3番の「"Universal CRTを使用するソフトウェアを配布する」を参照してください。

  3. をインストールします。  Windowsソフトウェア開発キット(SDK) for Windows 10  を読み、必要なDLLを含むように.specファイルを展開します。上記のリンクの6番にある "ユニバーサルCRTを使用するソフトウェアの配布"を参照してください。

Pythonモジュールのパッケージングに関する問題

プログラムから呼び出されるパッケージが多く、パッケージングに問題がある可能性があり、パッケージ化されたプログラムが正しく実行されるようにするために行うべきことがあります。

1. PyQtプラグインの欠落

PyQt を使って UI インタフェースを書いている Python のコードは、パッケージ化されるときにいくつかの特別な問題が発生することがあります。

PyQt を使用するパッケージャを実行すると、以下のように Qt platfrom プラグイン "windows" がないことを示唆するエラーが発生することがよくあります。

パッケージ実行後、png形式のアイコンは正常に表示されますが、ico形式のアイコンは表示されません(すべてのアイコンと関連ファイルのパスの問題があり、利用できませんが、これは後で別途説明します)。

これらのエラーはどちらも、PyQt関連のダイナミックリンクライブラリのディレクトリがパッケージディレクトリに生成されていないことが原因なので、これらの必要なファイルをパッケージ生成ディレクトリにコピーすれば、プラグインが見つからない問題は解決します。PyQt5 で記述された Python ソフトウェアパッケージを例にとると、パッケージの完成後の 結果ディレクトリには PyQt5 フォルダが含まれるので、以下のように PyQt5³³plugins の内容 をすべてパッケージの結果ディレクトリにコピーしてください。これにより、PyQtプラグインが見つからない問題が解決されます。

2. ダイナミックリンクライブラリの欠落問題

より一般的には、パッケージング後に一部のダイナミックリンクライブラリが欠落し、以下のようなプログラム実行時のエラーが発生する場合があります。

ImportError: DLL load failed: The specified module was not found

パッケージング処理中に、これらのDLLが見つからなかったという、これに関連する警告メッセージ(lib not found)が通常表示されます。例えば、32ビット版のパッケージでは、scipyモジュールに関連するDLLファイルが見つからないことがあります。この場合、パッケージ化されたspecファイルにDLLパスを指定し、パッケージ化されたパスに関連付けられるようにする必要があります。

binaries=[('C:\\Program Files\\\Python36-32\\Lib\\\\site-packages\\\\scipy\\\\extra-dll','.')]

Analysisのバイナリはパッケージファイルにバイナリを追加し、足りないダイナミックリンクライブラリはこの方法でパッケージパスに自動的に追加することができます。

3. フォームスタイル変更の問題

PythonプログラムをLite環境でパッケージングする場合など、パッケージングされたプログラムを実行すると、パッケージング中にPyQtスタイルのダイナミックライブラリが見つからなかったために、フォームスタイルが古いwinスタイルに変更されてしまうことがあります。そこで、PythonディレクトリでLibsite-packages suitePyQt5 suiteQtpluginsstylesを見つけて、パッケージ結果ディレクトリにスタイルディレクトリ全体をコピーします。

4. UnicodeDecodeError(ユニコードデコードエラー

パッケージング時に同様のエラーが発生した場合。

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xce in position 122

パッケージのコマンドラインで chcp 65001 と入力し、パッケージコマンドを実行することで、コマンドラインが utf-8 文字を表示するように設定することができます。あるいは、pyinstaller パッケージの compat.py を修正し、報告されたエラーに対応する行によって、以下のようになります。

out = out.decode(encoding)

に変更する。

out = out.decode(encoding, 'replace')

パッキングパスの凍結

パッケージ化されたプログラムを実行すると、プログラムが使用するアイコンが表示されなかったり、プログラムが使用する関連ファイルが関連付けられなかったりすることがよく起こります。あるいは、パッケージ化されたローカルマシンでは正常に動作するが、他のマシンにパッケージ化されたプログラムを置くと問題が発生する。これらは、プログラムが使用するファイルのパスが変更されたことが原因と思われますので、パッケージング時に実行パスに従って"freeze"しておく必要があります。

1. 絶対パスを使用する

Python コードで外部ファイルを呼び出す際に絶対パスを使用すると、パッケージング時にパスを追跡できるため、パッケージ化されたプログラムをローカルマシンで実行しても基本的に問題ありません。しかし、ローカルマシンのリソースファイルが変更されたり、パッケージが他のマシンに適用されたりすると、リソースファイルは検索されなくなります。これは、Pythonソフトウェアのパッケージングと配布の方法として適切ではありません。

2. フローズンパスの使用

frozen_dir.py という名前の py ファイルを追加します。

# -*- coding: utf-8 -*-
"""
Created on Sat Aug 25 22:41:09 2018
frozen dir
@author: yanhua
"""
import sys
import os

def app_path():
    """Returns the base application path."""
    if hasattr(sys, 'frozen'):
        # Handles PyInstaller
        return os.path.dirname(sys.executable)
    return os.path.dirname(__file__)


app_path()関数は、プログラムの実行パスを返す関数ですが、このように相対パスを設定するのは、このファイルをプロジェクトファイルのルートに配置することを容易にするためです。

ソースコードの中でパスを使用する場合、app_path()の戻り値を基本パスとし、それ以外のパスはその相対パスとする。例として、この記事で使用する Python プロジェクトのパッケージは次のようになります。

import frozen_dir
SETUP_DIR = frozen_dir.app_path()

FONT_MSYH = matplotlib.font_manager.FontProperties(
                fname = SETUP_DIR + r'\data\fonts\msyh.ttf',
                size = 8)

DIR_HELP_DOC = SETUP_DIR + r'\data\docs'
DIR_HELP_VIDEO = SETUP_DIR + r'\data\videos'

パスをフリーズすることで、ベースディレクトリのデータディレクトリにあるフォント、ドキュメント、ビデオが使用されます。

メインアプリケーションでも同様の調整が行われ、パスの設定方法が変更された

import frozen_dir

SETUP_DIR = frozen_dir.app_path()+r'\lib'
sys.path.append(SETUP_DIR)

このような方法でパッケージ化すると、パッケージ化された実行ファイルを他のマシンで実行することができるようになります。

その他の問題

OSや実行環境の違いにより、pyinstallerのパッケージングには他にも様々な問題があるので、私がパッケージングで遭遇したその他の落とし穴をまとめます。

1. パーミッションの問題

通常、パッケージングを行う際、一部のファイルへのアクセスが拒否されたり、特定の操作を行うためのパーミッションがないなどの問題が発生します。これに対する解決策は、概ね以下の通りです。

a) 管理者権限でcmdまたは他のコマンドラインウィンドウを実行します。

b) アンチウイルスソフトを無効にする

c) 完全な特権を持つ管理者アカウントを使用する。

2. 中国語のパス

pyinstallerのパッケージングで中国語のパスを使用しても問題はありませんが、パッケージング時のエラーの可能性を減らすために、パッケージングで使用するリソースファイルやコードファイルのパスを英語に設定するようにしてください。

3. パッケージされるファイルのサイズ

Pythonが大きな実行ファイルにパッケージされることは避けられませんが、パッケージされた実行ファイルをできるだけ効率化する方法があります。

a) コード中の不要なインポートを減らす。例えば、from xxx import *.

b) 効率的な実行環境 (ネイティブの python 環境など) でパッケージ化し、不要な python パッケージがプログラムにパッケージ化されないよう、不足しているパッケージはすべてダウンロードします。特に、anaconda のような統合環境でパッケージングすると、より大きな結果を得ることができます。

c) UPXの使用