pykakasiを使用したPythonコードについて、PyInstallerで生成したバイナリの実行時エラー「KeyError: '_max_key_len_'」を回避する

# 記事の内容

pykakasiを使用したPythonコードについて、PyInstallerで生成した実行ファイルを実行したら、エラー「KeyError: '_max_key_len_'」が発生しました。
それを回避する方法を記録しておきます。
Linux（Ubuntu 18.04）、python 3.7.5で実行し、pipenvを使用しています。

※ (2020-02-27) エラー回避方法について、もっと良い方法がわかりましたので、末尾に追記しました。

## サンプルコード

[この記事のコード](https://qiita.com/kanedaq/items/c4e5cf5060748bd6d7e1)を改変して使用しました。以下の3ファイルを使用し、foo.pyをバイナリにしたものを起動することにします。

```foo.py

from mymod1 import bar
from mymod2 import hoge

bar("Hello!")
hoge("ほげ！")
```

```mymod1.py

def bar(s):
    print(f"bar: {s}")
```

```mymod2.py

import pykakasi

def hoge(s):
    kakasi = pykakasi.kakasi()

    kakasi.setMode('H', 'a')
    kakasi.setMode('K', 'a')
    kakasi.setMode('J', 'a')

    conv = kakasi.getConverter()
    print(conv.do(s))
```

## 実行時エラーが起きるまでの手順

端末で以下を実行して、モジュールをインストールします。

```bash

pipenv install setuptools pykakasi pyinstaller
```

現段階で存在するファイルは以下の通りです。

```bash

$ ls
Pipfile
foo.py
mymod1.py
mymod2.py
```

生成されたPipfileは以下の通り。

```Pipfile

[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]

[packages]
setuptools = "*"
pykakasi = "*"
pyinstaller = "*"

[requires]
python_version = "3.7"
```

端末で以下を実行して、実行ファイルを作成します。

```bash

pipenv run pyinstaller foo.py
```

lsでファイルを確認すると、buildディレクトリとdistディレクトリが生成されています。

```bash

$ ls
Pipfile
build
dist
foo.py
foo.spec
mymod1.py
mymod2.py
```

「dist/foo/foo」が実行ファイルですので、動かしてみると、以下のように「KeyError: '_max_key_len_'」が発生しました。

```bash

$ dist/foo/foo 
bar: Hello!
Traceback (most recent call last):
  File "foo.py", line 5, in <module>
    hoge("ほげ！")
  File "mymod2.py", line 10, in hoge
    conv = kakasi.getConverter()
  File "pykakasi/kakasi.py", line 91, in getConverter
  File "pykakasi/kanji.py", line 30, in __init__
  File "pykakasi/kanji.py", line 126, in __init__
KeyError: '_max_key_len_'
[6202] Failed to execute script foo
```

## 実行時エラーの回避

dist配下のpykakashiの辞書が不足しているのが原因でした。
dist配下には、以下のように辞書が1つ存在します。

```bash

$ ls dist/foo/pykakasi/data/
itaijidict3.db
```

一方で、pipenvによるインストール先には8つの辞書がありました。

```bash

$ ls `pipenv --venv`/lib/python3.7/site-packages/pykakasi/data/
hepburndict3.db
itaijidict3.db
kunreidict3.db
passportdict3.db
hepburnhira3.db
kanwadict4.db
kunreihira3.db
passporthira3.db
```

これをdist配下に上書きでコピーします。

```bash

$ cp `pipenv --venv`/lib/python3.7/site-packages/pykakasi/data/* dist/foo/pykakasi/data/
$ ls dist/foo/pykakasi/data/
hepburndict3.db
itaijidict3.db
kunreidict3.db
passportdict3.db
hepburnhira3.db
kanwadict4.db
kunreihira3.db
passporthira3.db
```

再度バイナリを実行すると、以下のように正常に動きました。

```bash

$ dist/foo/foo
bar: Hello!
hoge！
```

## (2020-02-27) 追記

もっと良い方法がわかりましたので、追記します。
以下の追記部分はWindowsで実行しました。

参考ページ（感謝します）
・[PyInstallerで実行ファイルにリソースを埋め込み](https://qiita.com/firedfly/items/f6de5cfb446da4b53eeb)

最初の実行ファイル生成により、foo.specファイルも生成されました。
このファイルを以下のように編集して、a.datasにpykakasi辞書ファイルをすべて追加します。
pykakasi辞書ファイルの場所は環境によって異なりますので、そこは書き換える必要があります（私の環境ではCドライブから下っていった場所）。

hiddenimportsにも手を加えていますが、これについては[こちらの記事](https://qiita.com/kanedaq/items/e65507878c52ad67d002)をご覧ください。

```foo.spec

# -*- mode: python ; coding: utf-8 -*-

block_cipher = None


a = Analysis(['foo.py'],
             pathex=['C:\\Users\\username\\PycharmProjects\\foo_project'],
             binaries=[],
             datas=[],
-             hiddenimports=[],
+             hiddenimports=['pkg_resources.py2_warn'],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
+a.datas += [('pykakasi\\data\\hepburndict3.db', 'C:\\Users\\username\\.virtualenvs\\d_predict-Evs_tHQX\\lib\\site-packages\\pykakasi\\data\\hepburndict3.db', 'DATA')]
+a.datas += [('pykakasi\\data\\hepburnhira3.db', 'C:\\Users\\username\\.virtualenvs\\d_predict-Evs_tHQX\\lib\\site-packages\\pykakasi\\data\\hepburnhira3.db', 'DATA')]
+a.datas += [('pykakasi\\data\\itaijidict3.db', 'C:\\Users\\username\\.virtualenvs\\d_predict-Evs_tHQX\\lib\\site-packages\\pykakasi\\data\\itaijidict3.db', 'DATA')]
+a.datas += [('pykakasi\\data\\kanwadict4.db', 'C:\\Users\\username\\.virtualenvs\\d_predict-Evs_tHQX\\lib\\site-packages\\pykakasi\\data\\kanwadict4.db', 'DATA')]
+a.datas += [('pykakasi\\data\\kunreidict3.db', 'C:\\Users\\username\\.virtualenvs\\d_predict-Evs_tHQX\\lib\\site-packages\\pykakasi\\data\\kunreidict3.db', 'DATA')]
+a.datas += [('pykakasi\\data\\kunreihira3.db', 'C:\\Users\\username\\.virtualenvs\\d_predict-Evs_tHQX\\lib\\site-packages\\pykakasi\\data\\kunreihira3.db', 'DATA')]
+a.datas += [('pykakasi\\data\\passportdict3.db', 'C:\\Users\\username\\.virtualenvs\\d_predict-Evs_tHQX\\lib\\site-packages\\pykakasi\\data\\passportdict3.db', 'DATA')]
+a.datas += [('pykakasi\\data\\passporthira3.db', 'C:\\Users\\username\\.virtualenvs\\d_predict-Evs_tHQX\\lib\\site-packages\\pykakasi\\data\\passporthira3.db', 'DATA')]
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          [],
          exclude_binaries=True,
          name='foo',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          console=True )
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               upx_exclude=[],
               name='foo')
```

書き換えたspecファイルを使用して、再び実行ファイルを生成します。

```bash

pipenv run pyinstaller foo.spec
```

生成されたバイナリを実行すると、以下のように正常に動きました。

```
C:\Users\username\PycharmProjects\foo_project>dist\foo\foo
bar: Hello!
hoge！
```

バイナリのあるdistディレクトリ配下には、以下のようにpykakasi用のサブディレクトリが生成されて、辞書がコピーされているのが確認できます。

```
C:\Users\username\PycharmProjects\foo_project>dir .\dist\foo\pykakasi\data

 C:\Users\username\PycharmProjects\foo_project\dist\foo\pykakasi\data のディレクトリ

    <DIR>          .
    <DIR>          ..
             5,852 hepburndict3.db
             5,649 hepburnhira3.db
            13,981 itaijidict3.db
         7,154,489 kanwadict4.db
             5,843 kunreidict3.db
             5,642 kunreihira3.db
             6,635 passportdict3.db
             6,370 passporthira3.db
```

追記は以上です。