Macのpyenv環境下でtkinterを使えるようにする方法

# この記事について
pyenv環境下でのPythonでmatplotlibを使おうとすると、以下のようなエラーが出る場合があります。

```bash
$ python main.py
(略)
ModuleNotFoundError: No module named '_tkinter'
```

「tkinterというパッケージがない」というエラーです。しかし、tkinterはPythonの標準パッケージなはず。なぜだろう？と思う方もいるでしょう。
これは、tkinterがない、というよりはtkinterの依存先であるTcl/TkというGUIツールがないことで起こっています。
(tkinterはTcl/TkのGUIをPythonで扱うためのインターフェースなので、Tcl/TkがPCに入っていないと動かないのは当然です。)

この記事では、これを解決しpyenv下でmatplotlibを使えるようにセットアップする方法を紹介します。

## 使用する環境・バージョン
- OS: macOS Catalina 10.15.7
- pyenv 1.2.21
- tcl-tk: stable 8.6.10 (bottled) [keg-only]

# エラー解消のStep
## 0. (下準備)pipで入れたパッケージリストを保存しておく
これからpyenvで入れたPythonを一回抜いて再インストールする必要があります。
そのため現状復帰が楽にできるように、pipで入れたパッケージのリストを出しておきましょう。

以下のコマンドを実行します。

```bash
$ pip freeze > pip.txt
```
こうすることで、`pip.txt`に今どのパッケージがどのバージョンで入っているのかが全て書き出されます。

## 1. pyenvで入れたPythonのアンインストール
pipパッケージリストの保存ができたら、一回pyenvで入れたPythonを抜きます。

```bash
# 一旦システムのPythonに切り替え
$ pyenv global system
$ pyenv versions
* system
  3.X.X (set by /Users/myname/.pyenv/version)

# 3.X.Xになっていないことを確認したらアンインストール
$ pyenv uninstall 3.X.X
```

## 2. tcl-tkのインストール
tkinterインターフェースの依存先であるtcl-tkをbrewでインストールします。

```bash
$ brew install tcl-tk
(略)
==> tcl-tk
tcl-tk is keg-only, which means it was not symlinked into /usr/local,
because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.

If you need to have tcl-tk first in your PATH run:
  echo 'export PATH="/usr/local/opt/tcl-tk/bin:$PATH"' >> /Users/myname/.bash_profile

For compilers to find tcl-tk you may need to set:
  export LDFLAGS="-L/usr/local/opt/tcl-tk/lib"
  export CPPFLAGS="-I/usr/local/opt/tcl-tk/include"

For pkg-config to find tcl-tk you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/tcl-tk/lib/pkgconfig"
```
`brew install tcl-tk`を実行すると、このように「これからtcl-tkを使うためにはどのような設定を行えばいいのか」という説明書きが画面に出力されます。
なので、ここで表示された内容はどこかに書き留めておくといいでしょう。

仮にこの内容を忘れてしまって、後からこの設定を確認したいという場合は、`brew info`コマンドを打てば同内容を再び見ることができます。

```bash
$ brew info tcl-tk
```

## 3. tcl-tkの設定
先ほど表示された説明書きに従って、以下の内容を`.bash_profile`に追記します。

```.bash_profile
# 以下の内容を追記

export PATH="/usr/local/opt/tcl-tk/bin:$PATH"
export LDFLAGS="-L/usr/local/opt/tcl-tk/lib"
export CPPFLAGS="-I/usr/local/opt/tcl-tk/include"
export PKG_CONFIG_PATH="/usr/local/opt/tcl-tk/lib/pkgconfig"
```

次のコマンドで、`.bash_profile`をviで開くことができるので、今の内容を書き込みましょう。

```bash
$ vi .bash_profile
```

## 4. Pythonの再インストール
tcl-tkの設定が終わったら、再びpyenvでPythonをインストールしましょう。

ただ、この`pyenv install`の際に以下のようなオプションコマンドをつける必要があります。

```bash
$ PYTHON_CONFIGURE_OPTS="--with-tcltk-includes='-I/usr/local/opt/tcl-tk/include' --with-tcltk-libs='-L/usr/local/opt/tcl-tk/lib -ltcl8.6 -ltk8.6'" pyenv install 3.X.X
```
出典:[GitHub pyenv/pyenv Issue#1375 pyenv install doesn't work with homebrew installed tcl-tk](https://github.com/pyenv/pyenv/issues/1375)

### 補足: このオプションコマンドは何をしているのか
実は、このオプションなしに`pyenv install 3.X.X`とだけでthinterを使おうとすると、以下のようなエラーが出ます。

```
RuntimeError: tk.h version (8.6) doesn't match libtk.a version (8.5)
```
これは、brewでインストールしたtcl-tkがversin 8.6なのに対し、pyenvが使うデフォルトのtcl-tkが8.5であることに起因しています。
そのため、あのようなオプションをつけることで、「version8.6のtcl-tkを使ってPythonをビルドするように」という設定をしているのです。

## 5. 動作確認
実際にエラーが解消されたかどうかを見てみましょう。
先ほどインストールしたPythonバージョンに切り替えてから確認します。

```bash
$ pyenv global 3.X.X
$ pyenv versions
  system
* 3.7.4 (set by /Users/myname/.pyenv/version)
```

### tkinterが使えるかどうか
以下のコマンドを打つことで、今使用しているPythonでthinterが使えるかどうか確認することができます。

```bash
$ python -m tkinter
```
無事にthinterが使えるようになっているなら、以下のようなGUIが自動で開かれるはずです。
![スクリーンショット 2020-12-10 0.49.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/63ac1369-1902-6af1-d4a7-3427bbfc8d33.png)

### matplotlibが使えるかどうか
tkinterが入ったことは確認できましたが、matplotlibはどうでしょうか。

まずは、matplotlib含む、pipで入れたパッケージを戻しましょう。先ほど`pip.txt`にパッケージ一覧を保存しておいたので、それを元に同一パッケージ・同一バージョンのものを一気に再インストールします。

```bash
$ pip install -r pip.txt
```
パッケージを戻したら、実際にmatplotlibを使ったpythonコードを実行してみます。
以下のコードは、[matplotlibの公式Tutorial](https://matplotlib.org/3.3.3/tutorials/introductory/usage.html)から持ってきたものです。

```python
#coding: utf-8
import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots()  # Create a figure containing a single axes.
ax.plot([1, 2, 3, 4], [1, 4, 2, 3])  # Plot some data on the axes.

plt.savefig('figure.png')
```

![figure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/d26d544b-8f1c-bd37-67c3-d68e9a6046e3.png)
エラーが出ず、このような画像ファイルが生成されればOKです。お疲れ様でした。