LoginSignup
32

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-03-31

この記事は札幌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

PyQtPySideがある。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とする。

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++ソースを用意

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を使って簡単にできる。それが

    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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32