Pyinstaller によるPython 3.6スクリプトのexeファイル化

# サマリ

- Sklearn, matplotlib等を利用したPythonスクリプトを Pyinstallerでexeファイル化した際のはまったポイントと対処方法の備忘録。


## 環境
- MS Windows 10 Pro (32bit, 1709)
- Anaconda3-5.0.1-Windows-x86.exe (Python 3.6.3)
- pycharm-community-2017.3.2.exe


## はまったポイント

- Python3.6対応
- Sklearn 対応
- Multiprocessing 対応
- Matplotlibを使ったグラフ描画 対応


# 詳細

## Python3.6 対応

### 問題点

Python 3.6スクリプトをPyinstallerでコンパイルすると、下記メッセージがでて失敗する。

```
AttributeError: 'str' object has no attribute 'items'
```

### 対処方法

最新のPyinstaller 3.3.1 を導入。

```
pip install --upgrade pyinstaller
```

### 参照サイト

https://qiita.com/y-tsutsu/items/f687cf4b57442557aade
https://github.com/pyinstaller/pyinstaller/releases


## Sklearn対応

### 問題点
Pyinstaller によるコンパイルは成功するものの、コンパイルしたexeファイルを実行すると、下記エラーメッセージが発生。

```
ModuleNotFoundError: No module named 'typedefs'
[7436] Failed to execute script myprog
```

### 対処方法

- コンパイル --> exeファイル実行時エラーメッセージで出てきたmodule を myprog.spec のhiddenimports に追加 --> 再コンパイル、を繰り返す。
- myprog.spec は 最初のコンパイル時に生成される。

※ より効率が良い方法もあるようだが、それではうまく行かなかった。

#### 1回目 エラーメッセージ
```
  File "sklearn\neighbors\dist_metrics.pyx", line 52, in init sklearn.neighbors.dist_metrics
ModuleNotFoundError: No module named 'typedefs'
[7436] Failed to execute script myprog
```

#### 2回目 エラーメッセージ
```
  File "sklearn\neighbors\quad_tree.pxd", line 54, in init sklearn.tree._tree
ModuleNotFoundError: No module named 'sklearn.neighbors.quad_tree'
[592] Failed to execute script myprog
```

#### 3回目 エラーメッセージ
```
  File "sklearn\tree\_utils.pxd", line 78, in init sklearn.neighbors.quad_tree
ModuleNotFoundError: No module named 'sklearn.tree._utils'
[2732] Failed to execute script myprog
```

### 参考サイト

https://stackoverflow.com/questions/44136159/no-module-name-typedefs-when-use-pyinstaller

https://stackoverflow.com/questions/15114695/pyinstaller-import-error
より効率の良いやり方？
https://stackoverflow.com/questions/35478526/pyinstaller-numpy-intel-mkl-fatal-error-cannot-load-mkl-intel-thread-dll

## Multiprocessing対応

### 問題

Multiprocessing.pool関数を用いたスクリプトをコンパイルし、実行しても以下のエラーがでて失敗する。

```
Error: no such option: --multiprocessing-fork
```

### 対処方法

- UPX化 のDisable
- Myprog.py に `multiprocessing.freeze_support()` を追加（下記まとめ `myprog.py` 参照）。

※ 参考サイトでは onefileの場合は上記対処だけでは無理と記述があったが、手元ではできた。

### 参考サイト

https://stackoverflow.com/questions/24944558/pyinstaller-built-windows-exe-fails-with-multiprocessing


## Matplotlibを使ったグラフ描画

### 問題

Matplotlibを用いたグラフ描画スクリプトをコンパイルし、実行しても以下のエラーがでて失敗する。

```
This application failed to start because it could not find or load the Qt platform plugin "windows"
in "".

Reinstalling the application may fix this problem.
```

### 対処方法

- PyQt5のインストール


```
# pip install PyQt5
Collecting PyQt5
  Using cached PyQt5-5.9.2-5.9.3-cp35.cp36.cp37-none-win32.whl
Collecting sip<4.20,>=4.19.4 (from PyQt5)
  Using cached sip-4.19.6-cp36-none-win32.whl
Installing collected packages: sip, PyQt5
Successfully installed PyQt5-5.9.2 sip-4.19.6
```


# まとめ

## myprog.py (一部抜粋）

- Multiprocessing対応コードの追加

```python:myprog.py
import multiprocessing
import sys

<メイン処理>

if __name__ == '__main__':
    if sys.platform.startswith('win'):
        # On Windows calling this function is necessary.
        multiprocessing.freeze_support()

    <メイン処理呼び出し>

```

## myprog.spec 

- `pyinstaller myprog.py` で生成された`myprog.spec`を修正。
- Sklearn対応のため、実行時ModuleNotFoundErrorエラーがなくなるまで、hiddenimports にモジュールを追加。
- Multiprocessing対応のため、`upx=False`とする。

```python:myprog.spec
# -*- mode: python -*-

block_cipher = None

himports = [
    'cython', 'sklearn', 'sklearn.neighbors.typedefs', 'sklearn.neighbors.quad_tree', 'sklearn.tree._utils']

a = Analysis(['myprog.py'],
             pathex=['C:\\Users\\XXXXX\\PycharmProjects\\myprog'],
             binaries=[],
             datas=[],
             hiddenimports=himports,
             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,
          a.binaries,
          a.zipfiles,
          a.datas,
          name='myprog',
          debug=False,
          strip=False,
          upx=False,
          runtime_tmpdir=None,
          console=True)
```

## コンパイル方法

```
pyinstaller myprog.spec
```