boost.python: how to pass a python object to C++ world and how to return a C++ created object to the python interpreter
この話はプログラム言語の話なのでこの話題に興味のない方はまたの機会にお会いしましょう.
私はこれまでスクリプティング言語として ruby を使ってきたのだが,industry では python という言語がさかんであるので,python も使ってみることにした.以前はプログラミング言語をかじった(ほとんど三日坊主でマスターしたものはあまりないが)ものだったが,最近はそんなでもない.年をとったものである.そこでちょっと皆のすなる python というものを私もかじってみることにした.
最初に,Marc Lutz の Learning Python を読んだ.(二週間弱かかった).なかなか面白いと思った.先週本を読み終えたので,今週はプログラム書いてみることにした.
Python には様々な入門の Page があるので,私が言語の話をしても読者はそんなに興味を持たれないであろう.そこでちょっと細かい話になるが,boost.python の話をしよう.現在私は boost.phthon を使ってあるライブラリを python に bind している.
boost.pythonは C++ のプログラムから Python interpreter を呼び出したり,Python のプログラムから C++ の関数を呼び出したりするためのコードを書くためのライブラリである.これを使うと Python の機能を拡張するというようなことができる.
boost.python のドキュメントある程度充実しているが,例題が多少乏しい感じがする.とはいっても,boost.python を使う人はそんなに多くないだろうから仕方ないことだろう.
次の例題(passobj_mod.cpp)は,Python interpreter から python のdictionary を私の C++ のobject に渡すことができるかのテストである.boost.python はとても良くできていて,そういうことが簡単に書けることがわかった.
---
/// passobj_mod.cpp: How to pass the python dict to the C++ fucntion/method.
///
/// Copyright (C) 2010 Shitohichi Umaya
///
/// test for using C++ class from python: pass a string or a dict to a
/// C++ method
#include <boost/python.hpp>
#include <boost/python/object.hpp>
#include <boost/python/extract.hpp>
#include <boost/python/list.hpp>
#include <boost/python/dict.hpp>
#include <boost/python/str.hpp>
#include <stdexcept>
#include <iostream>
/// using namespace only for example
using namespace boost::python;
/// C++ object which python can instantiate
class PassObj {
public:
/// constructor
PassObj()
{
// empty
}
/// pass a python object, but this should be a python dictionary.
/// \param[in] pydict a dictionary
void pass_dict(object pydict) const {
extract< dict > cppdict_ext(pydict);
if(!cppdict_ext.check()){
throw std::runtime_error(
"PassObj::pass_dict: type error: not a python dict.");
}
dict cppdict = cppdict_ext();
list keylist = cppdict.keys();
// careful with boost name. there already have a conflict.
int const len = boost::python::len(keylist);
std::cout << "len(keylist) = " << len << std::endl;
for(int i = 0; i < len; ++i){
// operator[] is in python::boost::object
std::string keystr = extract< std::string >(str(keylist[i]));
std::string valstr = extract< std::string >(str(cppdict[keylist[i]]));
std::cout << "key:[" << keystr << "]->[" << valstr << "]" << std::endl;
}
}
/// pass a python object, but this should be a python string.
/// \param[in] pydict a string
void pass_string(object pystr) const {
extract< std::string > cppstr_ext(pystr);
if(!cppstr_ext.check()){
throw std::runtime_error(
"PassObj::pass_str: type error: not a python string.");
}
std::string cppstr = cppstr_ext();
std::cout << "passed string: " << cppstr << std::endl;
}
/// return a dict object. Does this works?
/// \return a dict object
object return_dict() const {
dict cppdict;
cppdict["this"] = "work?";
cppdict["no"] = "idea";
cppdict["number"] = 1;
return cppdict;
}
/// return a dict object. Does this works?
/// \return a dict object
object return_string() const {
return str("Incredible, this works.");
}
private:
};
/// importing module name is 'passobj_mod'
BOOST_PYTHON_MODULE(passobj_mod)
{
class_<PassObj>("passobj")
.def("pass_dict",
&PassObj::pass_dict,
"pass python dict object to c++ method")
.def("pass_string",
&PassObj::pass_string,
"pass python string to c++ method")
.def("return_dict",
&PassObj::return_dict,
"return C++ created dict to python")
.def("return_string",
&PassObj::return_string,
"return C++ created str to python")
;
}
---
#
# test_passobj_mod.py
# test pass object module, python side implementation
# Copyright (C) 2010 Shitohichi Umaya
#
import passobj_mod
pobj = passobj_mod.passobj()
print dir(pobj)
pobj.pass_string('This is python string, can you hear me?')
pdict = {'pythondict': 1, 'foo': 'bar', 'Bach': 'Goldberg Variation' }
pobj.pass_dict(pdict)
dict_from_cpp = pobj.return_dict()
str_from_cpp = pobj.return_string()
---
#! /bin/sh -x
# build.sh
# Copyright (C) 2010 Shitohichi Umaya
#
PYTHON_INCLUDE=`python-config --includes`
MOD_CPP_SOURCE_BASE=passobj_mod
g++ ${PYTHON_INCLUDE} -DPIC -shared -fPIC ${MOD_CPP_SOURCE_BASE}.cpp -o ${MOD_CPP_SOURCE_BASE}.so -lboost_python
---
この test_passobj_mod.py は passobj_mod.cpp の PassObj の method を呼ぶテストである.build.sh は passobj_mod.so をビルドする sh script である.ファイルを作成して実行権限などを変更した後,以下のようにタイプする
# build.sh
# python test_passobj_mod.py
すると,実際に動かすことができるかと思う.動作確認は Ubuntu Linux 9.04 にて行なった.
さて,test_passobj_mod.py は本の例題などを除いた私のほぼ初めてのpython プログラムであることもあって class も def もないが,いつになってもプログラムが思ったように動いた時は楽しいものだと思う.
これは計算機言語の細かい話なので,興味のない方は飛ばしたほうがいいでしょう.
C++ には unsigned という修飾子がある.これは符号無しの意味で,unsigned int のように使う.たとえば配列の index は負の値は普通意味がないので,この型を使ったりする.bit array の領域として使うには問題がないが,1 bit 欲しさにこれを int を unsigned int にするのは通常良くない考えである.(*) 特に implicit conversion が入ってくるとややこしい.計算結果が underflow しても符号無しというのは数としては直感的ではないと思うからである.
たとえば,unsigned int の -1 は 32bit マシンでは 4294967295 になることが多い.これは多くの計算機の実装がそうなるためである.このような計算機の内部表現を知っている人が書いたコードでも,ある日32bitで動いていたコードが64bit 環境に移った途端,動かなくなることがある.たとえば,size_t とunsigned int を交換可能なように書いているコードが,-1 を illegal な値として使う,ということをした場合である.
次の例が 32 bit で動いても 64 bit では動かないことがあるコードの例である.
---
#include <iostream>
#include <vector>
void foo(std::vector< int > & vec, size_t idx){
if(idx == size_t(-1)){
std::cout << "Illegal index" << std::endl;
return;
}
std::cout << "OK! accessing a vector with idx = " << idx << std::endl;
// vec[idx] = ...
}
int main()
{
std::vector< int > vec;
unsigned int idx = -1; // illegal index
foo(vec, idx);
unsigned int uint_minus_1(-1);
size_t size_t_minus_1(-1);
size_t size_t_casted = static_cast< size_t >(uint_minus_1);
std::cout << "(unsigned int)(-1) = " << uint_minus_1 << std::endl;
std::cout << "size_t(-1) = " << size_t_minus_1 << std::endl;
std::cout << "size_t(-1) (casted) = " << size_t_casted << std::endl;
}
---
結果は以下のようになる (64bit Linux)
---
nvlp[16]bash % ./unsigned_fail
OK! accessing a vector with idx = 4294967295
(unsigned int)(-1) = 4294967295
size_t(-1) = 18446744073709551615
size_t(-1) (casted) = 4294967295
---
まず,unsigned のものに -1 という signed の値を渡すこと自体に問題がある.また,このコードの問題は implicit な型の変換が行なわれていることにもある.C++ では unsigned に -1 を代入すると,型によって値が異なる.これはとても難しいことだと思う.最近の compiler はこれに warning を出してくれることがあるが,この warning は実は本当のバグかもしれないので注意している.unsigned is evil という友人がいた.私もできるだけ避けるようにしている.
計算機アーキテクチャを学ぶと,-1 と 4294967295 は等しい,ということが自然に思えてしまう.しかし最近私はできるだけ,それは実はおかしい,と思うようにしている.この例のように,その方がポータブルなコードが書けるからである.
(*) Bjarne Stroustrup, C++ Programming Language 3rd Ed. Section 4.4, paragraph 2. p.73
C++ には unsigned という修飾子がある.これは符号無しの意味で,unsigned int のように使う.たとえば配列の index は負の値は普通意味がないので,この型を使ったりする.bit array の領域として使うには問題がないが,1 bit 欲しさにこれを int を unsigned int にするのは通常良くない考えである.(*) 特に implicit conversion が入ってくるとややこしい.計算結果が underflow しても符号無しというのは数としては直感的ではないと思うからである.
たとえば,unsigned int の -1 は 32bit マシンでは 4294967295 になることが多い.これは多くの計算機の実装がそうなるためである.このような計算機の内部表現を知っている人が書いたコードでも,ある日32bitで動いていたコードが64bit 環境に移った途端,動かなくなることがある.たとえば,size_t とunsigned int を交換可能なように書いているコードが,-1 を illegal な値として使う,ということをした場合である.
次の例が 32 bit で動いても 64 bit では動かないことがあるコードの例である.
---
#include <iostream>
#include <vector>
void foo(std::vector< int > & vec, size_t idx){
if(idx == size_t(-1)){
std::cout << "Illegal index" << std::endl;
return;
}
std::cout << "OK! accessing a vector with idx = " << idx << std::endl;
// vec[idx] = ...
}
int main()
{
std::vector< int > vec;
unsigned int idx = -1; // illegal index
foo(vec, idx);
unsigned int uint_minus_1(-1);
size_t size_t_minus_1(-1);
size_t size_t_casted = static_cast< size_t >(uint_minus_1);
std::cout << "(unsigned int)(-1) = " << uint_minus_1 << std::endl;
std::cout << "size_t(-1) = " << size_t_minus_1 << std::endl;
std::cout << "size_t(-1) (casted) = " << size_t_casted << std::endl;
}
---
結果は以下のようになる (64bit Linux)
---
nvlp[16]bash % ./unsigned_fail
OK! accessing a vector with idx = 4294967295
(unsigned int)(-1) = 4294967295
size_t(-1) = 18446744073709551615
size_t(-1) (casted) = 4294967295
---
まず,unsigned のものに -1 という signed の値を渡すこと自体に問題がある.また,このコードの問題は implicit な型の変換が行なわれていることにもある.C++ では unsigned に -1 を代入すると,型によって値が異なる.これはとても難しいことだと思う.最近の compiler はこれに warning を出してくれることがあるが,この warning は実は本当のバグかもしれないので注意している.unsigned is evil という友人がいた.私もできるだけ避けるようにしている.
計算機アーキテクチャを学ぶと,-1 と 4294967295 は等しい,ということが自然に思えてしまう.しかし最近私はできるだけ,それは実はおかしい,と思うようにしている.この例のように,その方がポータブルなコードが書けるからである.
(*) Bjarne Stroustrup, C++ Programming Language 3rd Ed. Section 4.4, paragraph 2. p.73
p.116 4.6 Adjoint operator
少し戻るが Adjoint operator が後でも重要になってきているのでコメントしておこう.この operator が Hermitian として書かれている.これは conjugate transpose なので,複素数が関係しているのか,つまり虚数エネルギーなどを扱うのかとか思ってしまう.Hilbert 空間の話があったので,それと関連しているかもしれない.しかし,7 章まではこれは symmetry を意味しているにすぎないようだ.光輸送方程式では,光が光源から来て反射していくのをカメラで捕えた場合,この光源と光を交換しても良いということを示している.
謝辞
Carsten W. にこれで良いかと尋ねたらそうであろうとのコメントを頂いた.感謝します.
p.122 particle tracing 式 4.32のコメントの補足
私の blog ではこれをややこしく説明したが,なんのことはない,p.226 の式(8.9) そのものだ.この説明は簡潔で正確である.しかも,一般的である.というのも私は2つの加算を説明したが,この Veach の式は積分型で全ての場合に対応しているからだ.なんとも簡潔に説明できるものだなあ.
p.226 vertex
vertex (頂点)と聞くとつい三角形の頂点を想像してしまうのだが,この論文ではこれはサンプリングポイントである.サンプリングポイントは edge で接続されている.人によっては頂点数を距離とすることもあるが,Veach では edge 数を距離としているようだ.これは数字が一つ違うだけだが,違いがある.
少し戻るが Adjoint operator が後でも重要になってきているのでコメントしておこう.この operator が Hermitian として書かれている.これは conjugate transpose なので,複素数が関係しているのか,つまり虚数エネルギーなどを扱うのかとか思ってしまう.Hilbert 空間の話があったので,それと関連しているかもしれない.しかし,7 章まではこれは symmetry を意味しているにすぎないようだ.光輸送方程式では,光が光源から来て反射していくのをカメラで捕えた場合,この光源と光を交換しても良いということを示している.
謝辞
Carsten W. にこれで良いかと尋ねたらそうであろうとのコメントを頂いた.感謝します.
p.122 particle tracing 式 4.32のコメントの補足
私の blog ではこれをややこしく説明したが,なんのことはない,p.226 の式(8.9) そのものだ.この説明は簡潔で正確である.しかも,一般的である.というのも私は2つの加算を説明したが,この Veach の式は積分型で全ての場合に対応しているからだ.なんとも簡潔に説明できるものだなあ.
p.226 vertex
vertex (頂点)と聞くとつい三角形の頂点を想像してしまうのだが,この論文ではこれはサンプリングポイントである.サンプリングポイントは edge で接続されている.人によっては頂点数を距離とすることもあるが,Veach では edge 数を距離としているようだ.これは数字が一つ違うだけだが,違いがある.