Embedded Python in C++ / C++からPythonスクリプトを呼び出す

この記事は札幌C++勉強会のオンラインもくもく会で書きました。
札幌C++勉強会では、もくもく会の参加者を募集しております。
詳しくは@ignis_fatuusまで

# 概要
C++ですべてをやるのはつらい。だから部分的にPythonで楽をする。Boost.Python使えばできる。試しにTwetter APIでも使ってみた。

# Background
Pythonは便利。PythonでGUIなアプリを作りたい事情が筆者にあるが、困った問題がある。QtとPythonのbindingライブラリだ。
## GUIライブラリのPython Bindings
PythonでGUIを使うにはQtやGtkなどの、C言語またはC++用のGUIライブラリを使う。これら他言語のライブラリをPythonから呼び出す仕組みをbindingsと呼んだりする。Qtはとても有名なC++用のGUIライブラリで、これ選んでおけばあまり大きくハズレはしない、これを使いたい。Gtkやその他の選択肢もあるが、まあQtが無難。
## PythonのQt bindings
[PyQt](https://riverbankcomputing.com/software/pyqt/intro)や[PySide](https://wiki.qt.io/PySide)がある。Qtの最新版(2016/3/31現在)であるQt5を扱うにはPyQt5を利用すればいい。しかしPyQt5はGPLもしくは商用ライセンスなので避けたい。一方PySideは、最新の安定リリース版(2016/3/31時点)であるPySide 1.2.4はQt4.8までにしか対応していない。[PySide2](https://github.com/PySide/pyside2)ではQt5に対応するようだが、現時点ではWindowsでのビルドに失敗するようだ。

## GUIの部分はC++でやる。
以上のように、Pythonから使えて、筆者が納得できるGUIライブラリが現時点で存在しない。しかし、QtはもともとC++ライブラリなので、最初からC++で書けばフルに機能を使える。ところが全部をC++で書こうとすると非常にめんどくさい。従って、C++で書かねばならぬところ(今回のケースだとGUIの部分)はC++で書き、内部の大してパフォーマンスも要求されない部分はPythonで書く。例えばネットワーク通信はPythonでやる。なぜならC++でパフォーマンスを上げてもネットワーク遅延があるので大した利点がない。

## この記事で説明すること
Boost.Pythonを使ってC++からPythonを呼び出す方法を説明する。例として、Twitterのタイムラインを取得し、C++で標準出力に出すプログラムを紹介する。タイムラインの取得にはPythonのライブラリ、[tweepy](http://www.tweepy.org/)を利用する。

# インストール
## C++、Qt、Python、Boost.Python
WindowsのMsys2環境であればpacmanでインストールできる。

```bash
$ pacman -S mingw-w64-x86_64-gcc
$ pacman -S mingw-w64-x86_64-qt5
$ pacman -S mingw-w64-x86_64-python3
$ pacman -S mingw-w64-x86_64-gcc
```
## tweepy
```bash
$ pip3 install tweepy
```
もしかしたら、```python3 -m ensurepip --default-pip```とか必要だったかもしれない。pyvenv、virtualenvなどを使いたい場合は使っても問題ない。

# Pythonスクリプト
C++から呼び出されるPythonコードを書く。ファイル名は```embed.py```とする。

```py3:embed.py
#!/usr/bin/env python3
#vim:fileencoding=utf8
import sys
# pyvenvやAnacondaなどで、通常とは別の場所にtweepyをインストールした場合はその場所を指定する。
# sys.path.append('C:/Anaconda3/envs/MyEnvironments/Lib/site-packages')

# tokenはTwitterから取得しておく。
consumer_key        = "...."  
consumer_secret     = "...."
access_token        = "...."
access_token_secret = "...."

# tweepyの使い方は他の記事や公式サイトを参考にされたし
import tweepy
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)
api = tweepy.API(auth)

# この関数をC++から呼び出す
def timeline():
   public_tweets = api.home_timeline()
   tweets = [ (tweet.author.name, tweet.text) for tweet in public_tweets ]
   return tweets
```
まず最初に、tweepyやその依存ライブラリがインストールされているディレクトリを追加する。これはAnacondaやpyvenvなどを使っている場合は必要だが、使ってない場合は必要ないかもしれない。Twitter APIを利用するために必要なトークンは、Twitterサイトで開発者の登録をして申請して取得しておく。tweepyについては特に説明しない。最後に定義したtimelineがC++から呼び出す関数である。今回は、ツイート主の名前とツイート内容をタプルにし、そのリストを返すことでタイムラインのデータとした。

# C++ソースを用意
```cpp:twitter.cpp
#include<iostream>
#include<string>
#include<fstream>
#include<streambuf>
#include<boost/python.hpp>

# Pythonスクリプトでの(tweet.author.name, tweet.text)というタプル相当の構造体
struct Status {
    std::string name;
    std::string text;
    Status(const boost::python::tuple t) :
        name(boost::python::extract<std::string>(t[0])),
        text(boost::python::extract<std::string>(t[1]))
    { }
};

int main() {
    // PYTHONHOME環境変数を設定、Py_Initialize()の前に呼び出す
    // 環境によってはいらないのかもしれない。
    wchar_t python_home[] = L"c:\\msys64\\mingw64"; // python2ではcharでよい。python3だとwchar_t。
    Py_SetPythonHome(python_home);

    Py_Initialize();
    auto main_mod = boost::python::import("__main__");
    auto main_ns  = main_mod.attr("__dict__");

    // read python script
    std::ifstream ifs("embed.py");
    std::string script((std::istreambuf_iterator<char>(ifs)),
                        std::istreambuf_iterator<char>());
    // execute python script,
    // but not invoke timeline() function (definition only)
    boost::python::exec(script.c_str(), main_ns);

    // call python's timeline function and get the timeline data
    using input_iterator = boost::python::stl_input_iterator<boost::python::tuple>;
    auto timeline_data = main_mod.attr("timeline")();
    input_iterator begin(timeline_data), end;

    std::vector<Status> tweets(begin,end);
    for(auto tweet: tweets) {
        std::cout << tweet.name << ":" << tweet.text << std::endl;
    }
}
```
もくもく会の時間が尽きたので、簡単に説明する。
一番のポイントは「Pythonからどのようにデータを受け取るか」。これはBoost.PythonではIteratorを使って簡単にできる。それが

```cpp
    using input_iterator = boost::python::stl_input_iterator<boost::python::tuple>;
    auto timeline_data = main_mod.attr("timeline")();
    input_iterator begin(timeline_data), end;
    std::vector<Status> tweets(begin,end);
```
の部分。誠に申し訳ないがここでタイムアップである。
ソースを読んで察して欲しい。

追記：
ビルド方法の例だけ残す

```shell-session
g++ -o twitter.exe twitter.cpp -std=c++14 -I/mingw64/include/python3.5m -lboost_python3-mt -lpython3.5m

```