この記事は札幌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やPySideがある。Qtの最新版(2016/3/31現在)であるQt5を扱うにはPyQt5を利用すればいい。しかしPyQt5はGPLもしくは商用ライセンスなので避けたい。一方PySideは、最新の安定リリース版(2016/3/31時点)であるPySide 1.2.4はQt4.8までにしか対応していない。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を利用する。
インストール
C++、Qt、Python、Boost.Python
WindowsのMsys2環境であればpacmanでインストールできる。
$ 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
$ pip3 install tweepy
もしかしたら、python3 -m ensurepip --default-pip
とか必要だったかもしれない。pyvenv、virtualenvなどを使いたい場合は使っても問題ない。
Pythonスクリプト
C++から呼び出されるPythonコードを書く。ファイル名は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++ソースを用意
#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を使って簡単にできる。それが
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);
の部分。誠に申し訳ないがここでタイムアップである。
ソースを読んで察して欲しい。
追記:
ビルド方法の例だけ残す
g++ -o twitter.exe twitter.cpp -std=c++14 -I/mingw64/include/python3.5m -lboost_python3-mt -lpython3.5m