Python
Windows
PyQt5
PyInstaller

PyInstaller+PyQtでアイコン対応

はじめに

Pythonを使ってちょっとしたGUIアプリを作るときに,よくPyQtで書いてそれをPyInstallerでexeにパッケージングしています.
その時にお気に入りのアプリはアイコンもつけておきたいので,その方法についてまとめます.

対応したいアイコン表示は以下の二通りです.

  • エクスプローラに表示されるexeファイルのアイコン
  • タイトルバーやタスクバーに表示されるアイコン

開発環境

今回確認した環境は以下の通りです.

  • Windows10 Pro
  • Python 3.6.4
  • PyInstaller 3.3.1
  • PyQt5 5.10.1

インストール

PyInstallerとPyQtはpipからインストールができます.

> pip install pyinstaller pyqt5

exeファイルのアイコン

PyInstallerでパッケージングするときにアイコンをつけることができます.

> pyinstaller foo.py --onefile --noconsole --icon=bar.ico

--iconオプションにicoファイルを指定します.
またアイコン表示とは直接関係ありませんが,--onefileオプションでパッケージングするときに1ファイルにまとめるのと,--noconsoleオプションで起動時にコンソール画面を表示しないように指定しています.

foo.png

タイトルバーやタスクバーに表示されるアイコン

こちらはPyQtのコードで対応します.QApplicationのsetWindowIcon()に画像ファイルを渡すとタスクバーにアイコンが表示されます.

from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QWidget

app = QApplication(sys.argv)
app.setWindowIcon(QIcon('bar.ico')) # pngファイルなどでもOK
w = QWidget()
w.show()
sys.exit(app.exec())

これでPyInstallerで生成したexeファイルと同じパスにbar.icoを置いておくと,タイトルバーやタスクバーにアイコンが表示されます.
ただしこのままでは,exeファイルのほかにicoファイルも同時に配布する必要があるため,もう一工夫します.

画像ファイルの組み込み

PyInstallerでパッケージングするときに,外部ファイルを組み込むことができます.組み込むにはspecファイルに記述します.specファイルは一から記述してもいいですが,上記のようにfoo.pyファイルを指定してPyInstallerを動かしているとfoo.specファイルが生成されています.これに以下の一行を追記すると簡単です.

a = Analysis(['foo.py'],
             pathex=['C:\\work\\Python\\foo'],
             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)
+a.datas += [('bar.ico','.\\bar.ico', 'Data')]
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          name='foo',
          debug=False,
          strip=False,
          upx=True,
          runtime_tmpdir=None,
          console=False , icon='bar.ico')

このspecファイルを使用してPyInstallerを実行します.

> pyinstaller foo.spec

これでbar.icoファイルが組み込まれてパッケージングされます.

組み込んだファイルの読み込み

PyInstallerでパッケージングしたexeを実行するとTempフォルダに一時ファイルが展開されます.この中に上記で組み込んだ画像ファイルも展開されているので,これを読み込みます.

展開される先のパスはsys._MEIPASSから取得ができます.以下のようなコードを書いておけば,PyInstallerを使用したときと普通にPythonで実行したときとで両対応できます.

import os
import sys

def resource_path(relative):
    if hasattr(sys, '_MEIPASS'):
        return os.path.join(sys._MEIPASS, relative)
    return os.path.join(relative)

これを利用して以下のようなコードを書けば,パッケージングされたexeファイルだけでタイトルバーやタスクバーにアイコンを表示することができます.

import os
import sys

from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QWidget

def resource_path(relative):
    if hasattr(sys, '_MEIPASS'):
        return os.path.join(sys._MEIPASS, relative)
    return os.path.join(relative)

def main():
    app = QApplication(sys.argv)
    app.setWindowIcon(QIcon(resource_path('bar.ico')))
    w = QWidget()
    w.show()
    sys.exit(app.exec())

if __name__ == '__main__':
    main()

foo.png