44
boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. [email protected] R.Wobst, PyCon 2013 Köln 1/44

bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. [email protected] R.Wobst, PyCon

Embed Size (px)

Citation preview

Page 1: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

boost.python:

Nabelschnur zu PythonEin Erfahrungsbericht mit Rezepten

PyCon 2013, Köln

Reinhard Wobst. [email protected]

R.Wobst, PyCon 2013 Köln 1/44

Page 2: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

1.Was ist embedded Python?Hat nichts mit embedded Software zu tun, sondern ist ein Python-Interpreter, der von einer C-Schnittstelle aus gesteuert wird.

BEISPIEL:BEISPIEL: Editor vim, der mit --with-features=big (oder mit

python enabled) übersetzt wurde:

$ vim...:py a=3 # vim-Befehl...:py print a**3 # vim-Befehl27 # Ausgabe

R.Wobst, PyCon 2013 Köln 2/44

Page 3: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

Der Interpreter "lebt" innerhalb von vim und merkt sich seinen Zustand.Das Beispiel nützt wenig, aber:Der Interpreter kann auf Daten von vim zugreifen, etwa so:

vimdemo.py:

import vim

def _revlist(lst): for i in range(len(lst)-1, -1, -1): yield lst[i]

def reverse(): s = vim.current.line vim.current.line = ''.join(_revlist(s))

R.Wobst, PyCon 2013 Köln 3/44

Page 4: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

Ein Aufruf von reverse() dreht die aktuelle Zeile (in der der Cursor steht) um- in vim:

$ vim _textfile_...:py execfile('vimdemo.py') "define reverse():reverse()

Damit kann man sich beliebig komplexe Editorfunktionen in Python selbst programmieren (sofern erforderlich ☺).

Ermöglicht wird dies durch die mit Python gelieferte Python-C-Api, die nicht ganz so schwierig wie beim ersten Anschein ist. Dokumentation findet sich hier:

R.Wobst, PyCon 2013 Köln 4/44

Page 5: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

R.Wobst, PyCon 2013 Köln 5/44

Page 6: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

Einfaches Beispiel aus dem Tutorial:

#include <Python.h>

int main(int argc, char *argv[]){ Py_Initialize(); PyRun_SimpleString( "from time import time,ctime\n" "print 'Today is',ctime(time())\n"); Py_Finalize(); return 0;}

R.Wobst, PyCon 2013 Köln 6/44

Page 7: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

Arbeit mit Listen:

PyObject* PyList_New(Py_ssize_t len);int PyList_Append(PyObject *list, PyObject *item);

Problem:Referenzen müssen selbst verwaltet werden, Makros

Py_INCREF(x), Py_DECREF(x)

Chronische Fehlerquelle, sehr schwer zu lokalisieren!

R.Wobst, PyCon 2013 Köln 7/44

Page 8: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

2.Wozu braucht man das?Test eines Sternsensors (http://de.wikipedia.org/wiki/Sternsensor) - solch ein Gerät bestimmt anhand von Sternkarten seine Lage im Weltall auf 1...10 Bogensekunden innerhalb von Sekunden.

(Quelle: http://www.jena-optronik.de/de/lageregelungssensoren/sternsensor-astro-aps.html)

R.Wobst, PyCon 2013 Köln 8/44

Page 9: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

Das Gerät muss z.B. 15 Jahre lang im Orbit funktionieren.Tests sind daher extrem wichtig - aber die Testsoftware ist teils in C++ geschrieben und läuft auf großen Multicore-Systemen (um Datenmengen zu beherrschen und vorzufiltern), auch sind nicht alle Treiber frei.

Entwickler möchten Tests gern in Python schreiben (Begründung in diesem Rahmen überflüssig), brauchen jedoch "irgendwie" Zugang zu Treibern und Daten, möglichst sogar zu Klasseninstanzen.

R.Wobst, PyCon 2013 Köln 9/44

Page 10: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

3.boost.pythonboost ist eine Sammlung leistungsfähiger C++ - Bibliotheken (Release 1.52enthält etwa 80), u.a. zu linearer Algebra, Bildverarbeitung, Multithreading, regulären Ausdrücken u.v.a.m.

boost.python ist Teil des Boost-Projekts: eine C++ - Bibliothek zur "nahtlosen Kopplung" von C++ mit Python, die letztendlich auf der Python-C-API aufsetzt.

Homepage:

http://www.boost.org/doc/libs/1_44_0/libs/python/doc/index.html

Wiki (nützlich für den Anfang):

https://wiki.python.org/moin/boost.python > Embedding PythonHier wird nur auf die Anbindung an ein embedded Python eingegangen.

R.Wobst, PyCon 2013 Köln 10/44

Page 11: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

3.1. Fähigkeiteneinfacheres Interface als bei Python-C-API

BEISPIEL:BEISPIEL: Funktionalität des Python-Skripts

import randomprint random.random()

nachbilden:

R.Wobst, PyCon 2013 Köln 11/44

Page 12: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

#include <boost/python.hpp>#include <iostream>#include <Python.h>

int main() { Py_Initialize(); boost::python::object rand_mod = boost::python::import("random"); boost::python::object rand_func = rand_mod.attr("random"); boost::python::object rand2 = rand_func(); std::cout << boost::python::extract<double>(rand2) << std::endl; return 0;}

R.Wobst, PyCon 2013 Köln 12/44

Page 13: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

Referenzen werden automatisch verwaltet (wichtiger Vorteil)

grundlegende Python-Datentypen wie Listen, Tupel, Dictionaries können einfach von boost aus erzeugt und verwaltet werden

für obige Anwendung entscheidend: Klassentypen und sogar Klasseninstanzen von C++ lassen sich nach Python exportieren, und C++ - Klasseninstanzen lassen sich von Python aus verändern!Wir sehen weiter unten, wie das geht.

Die Python-C-API lässt sich parallel dazu nutzen und muss sogar mit verwendet werden (bei Py_Initialize() z.B.) - boost.python deckt (noch?) nicht den vollen API-Umfang ab.

R.Wobst, PyCon 2013 Köln 13/44

Page 14: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

3.2. ProblemeMangelhafte, komplizierte Dokumentation - Web ist voll von Hilferufen und mehr oder weniger guten Beispielen.Hier: Auch nur mehr oder weniger gute Beispiele, hoffentlich nah am Optimum ☺.

boost.python basiert wie auch boost viel auf Templates, die Anwender zur Verzweiflung treiben können. Beispiel einer nicht so seltenen Fehlermeldung:

R.Wobst, PyCon 2013 Köln 14/44

Page 15: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

/home/wobst/buss/.../third_party/boost/boost/python/object/value_holder.hpp: In constructor ‘boost::python::objects::value_holder<Value>::value_holder(PyObject*, A0) [with A0 = boost::reference_wrapper<const GlobPy::VfsReadNode>, Value = GlobPy::VfsReadNode, PyObject = _object]’:

/home/wobst/buss/.../third_party/boost/boost/python/object/make_instance.hpp:71:48: instantiated from ‘static Holder* boost::python::objects::make_instance<T, Holder>::construct(void*, PyObject*, boost::reference_wrapper<const T>) [with T = GlobPy::VfsReadNode, Holder = boost::python::objects::value_holder<GlobPy::VfsReadNode>, PyObject = _object]’

/home/wobst/buss/.../third_party/boost/boost/python/object/make_instance.hpp:45:13: instantiated from ‘static PyObject* boost::python::objects::make_instance_impl<T, Holder, Derived>::execute(Arg&) [with Arg = const boost::reference_wrapper<const GlobPy::VfsReadNode>, T = GlobPy::VfsReadNode, Holder = boost::python::objects::value_holder<GlobPy::VfsReadNode>, Derived = boost::python::objects::make_instance<GlobPy::VfsReadNode, boost::python::objects::value_holder<GlobPy::VfsReadNode> >, PyObject = _object]’

/home/wobst/buss/.../third_party/boost/boost/python/object/class_wrapper.hpp:29:51: instantiated from ‘static PyObject* boost::python::objects::class_cref_wrapper<Src, MakeInstance>::convert(const Src&) [with Src = GlobPy::VfsReadNode, MakeInstance = boost::python::objects::make_instance<GlobPy::VfsReadNode, boost::python::objects::value_holder<GlobPy::VfsReadNode> >, PyObject = _object]’

/home/wobst/buss/.../third_party/boost/boost/python/converter/as_to_python_function.hpp:27:9: instantiated from ‘static PyObject* boost::python::converter::as_to_python_function<T, ToPython>::convert(const void*) [with T = GlobPy::VfsReadNode, ToPython = boost::python::objects::class_cref_wrapper<GlobPy::VfsReadNode, boost::python::objects::make_instance<GlobPy::VfsReadNode,boost::python::objects::value_holder<GlobPy::VfsReadNode> > >, PyObject = _object]’

/home/wobst/buss/.../third_party/boost/boost/python/to_python_converter.hpp:87:5: instantiated from ‘boost::python::to_python_converter<T, Conversion, has_get_pytype>::to_python_converter() [with T = GlobPy::VfsReadNode, Conversion = boost::python::objects::class_cref_wrapper<GlobPy::VfsReadNode, boost::python::objects::make_instance<GlobPy::VfsReadNode, boost::python::objects::value_holder<GlobPy::VfsReadNode> > >, bool has_get_pytype = true]’

/home/wobst/buss/.../third_party/boost/boost/python/object/class_wrapper.hpp:26:1: instantiated from ‘static void boost::python::objects::class_metadata<T, X1, X2, X3>::maybe_register_class_to_python(T2*, mpl_::false_) [with T2 = GlobPy::VfsReadNode, T = GlobPy::VfsReadNode, X1 = boost::python::detail::not_specified, X2 = boost::python::detail::not_specified, X3 = boost::python::detail::not_specified, mpl_::false_ = mpl_::bool_<false>]’

/home/wobst/buss/.../third_party/boost/boost/python/object/class_metadata.hpp:229:9: instantiated from ‘static void boost::python::objects::class_metadata<T, X1, X2, X3>::register_aux2(T2*, Callback) [with T2 = GlobPy::VfsReadNode, Callback = boost::integral_constant<bool, false>, T = GlobPy::VfsReadNode, X1 = boost::python::detail::not_specified, X2 = boost::python::detail::not_specified, X3 = boost::python::detail::not_specified]’

R.Wobst, PyCon 2013 Köln 15/44

Page 16: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

/home/wobst/buss/.../third_party/boost/boost/python/object/class_metadata.hpp:219:9: instantiated from ‘static void boost::python::objects::class_metadata<T, X1, X2, X3>::register_aux(void*) [with T = GlobPy::VfsReadNode, X1 = boost::python::detail::not_specified, X2 = boost::python::detail::not_specified, X3 = boost::python::detail::not_specified]’

/home/wobst/buss/.../third_party/boost/boost/python/object/class_metadata.hpp:205:9: instantiated from ‘static void boost::python::objects::class_metadata<T, X1, X2, X3>::register_() [with T = GlobPy::VfsReadNode, X1 = boost::python::detail::not_specified, X2 = boost::python::detail::not_specified, X3 = boost::python::detail::not_specified]’

/home/wobst/buss/.../third_party/boost/boost/python/class.hpp:497:9: instantiated from ‘void boost::python::class_<T, X1, X2, X3>::initialize(const DefVisitor&) [with DefVisitor = boost::python::init_base<boost::python::init<std::basic_string<char> > >, W = GlobPy::VfsReadNode, X1 = boost::python::detail::not_specified, X2 = boost::python::detail::not_specified, X3 = boost::python::detail::not_specified]’

/home/wobst/buss/.../third_party/boost/boost/python/class.hpp:209:9: instantiated from ‘boost::python::class_<T, X1, X2, X3>::class_(const char*, const boost::python::init_base<DerivedT>&) [with DerivedT = boost::python::init<std::basic_string<char> >,W = GlobPy::VfsReadNode, X1 = boost::python::detail::not_specified, X2 = boost::python::detail::not_specified, X3 = boost::python::detail::not_specified]’

/home/wobst/buss/.../ut-ng/src/PythonApi/PythonApi.cpp:34:67: instantiated from here/home/wobst/buss/.../third_party/boost/boost/python/object/value_holder.hpp:137:13: error: no matching function for call to

‘GlobPy::VfsReadNode::VfsReadNode(const boost::reference_wrapper<const GlobPy::VfsReadNode>::type&)’/home/wobst/buss/.../ut-ng/src/PythonApi/pyvfs.hpp:72:9: note: candidates are: GlobPy::VfsReadNode::VfsReadNode(const

std::string&)/home/wobst/buss/.../ut-ng/src/PythonApi/pyvfs.hpp:59:1: note:

GlobPy::VfsReadNode::VfsReadNode(GlobPy::VfsReadNode&)/home/wobst/buss/.../third_party/boost/boost/system/error_code.hpp: At global scope:/home/wobst/buss/.../third_party/boost/boost/system/error_code.hpp:214:36: warning: ‘boost::system::posix_category’ defined but not

used/home/wobst/buss/.../third_party/boost/boost/system/error_code.hpp:215:36: warning: ‘boost::system::errno_ecat’ defined but not used/home/wobst/buss/.../third_party/boost/boost/system/error_code.hpp:216:36: warning: ‘boost::system::native_ecat’ defined but not used

R.Wobst, PyCon 2013 Köln 16/44

Page 17: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

Templates sind zwar typsicher, aber der Programmierer weiß trotzdem meist nicht, was hinter den Kulissen passiert - Nebeneffekte machen denNutzen oft fraglich.

boost.python verwendet sogar C-Makros, z.B. das wichtige

BOOST_PYTHON_MODULE, das etwa so expandiert:

R.Wobst, PyCon 2013 Köln 17/44

Page 18: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

BOOST_PYTHON_MODULE(WriteNode)

void init_module_WriteNode(); extern "C" __attribute__ \ ((visibility("default"))) \ void initWriteNode() { boost::python::detail::init_module \ ("WriteNode",&init_module_WriteNode); } void init_module_WriteNode() { ... }

R.Wobst, PyCon 2013 Köln 18/44

Page 19: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

Fazit: Wenn man einen Modulnamen WriteNode angibt, wird impliziert

eine neue Funktion initWriteNode() definiert, die später verwendetwerden muss - das ist in der Dokumentation zunächst nicht zu finden.

Compilezeiten: Insbesondere Templates treiben Compiler oft an ihre Grenzen, auch beim Speicherbedarf (der Autor verbrauchte damit zum ersten Mal mehr als 4 GB RAM, obwohl er sonst extensiv Gimp, Firefox und LibreOffice nutzt). Die Compilezeiten wachsen für Python-Programmierer auf nervige Längen. Kein Wunder:

Der Präprozesser generiert aus #include <iostream> etwa437 KB Code;

zum Vergleich bei C: #include <stdio.h> erzeugt 16 KB Code.

Templates generieren i.a. weitaus komplexeren Code.

Fehlerbehandlung: Bei Python-Exceptions wird immer nur die Ausnahme boost::python::error_already_set geworfen; wie man sich den Fehlertext holt: s.u.

R.Wobst, PyCon 2013 Köln 19/44

Page 20: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

4.Praktisches BeispielWir erzeugen eine Instanz der C++Klasse PythonApi::Mylog, von der

wir die Methoden write() und save() nutzen wollen, und übergeben

sie als Attribut eines Python-Moduls CPPlog (der nur als Namensraum existiert) dem embedded Python. Eine mögliche (hoffentlich fast optimale) Lösung kann so aussehen:

R.Wobst, PyCon 2013 Köln 20/44

Page 21: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

4.1. Python-Modul definierennamespace Py = ::boost::python;

BOOST_PYTHON_MODULE(CPPlog){ Py::class_<PythonApi::Mylog>("Pylog") .def("write", &PythonApi::Mylog::write) .def("save", &PythonApi::Mylog::save);}

Damit werden u.a. die Ausnahmebehandlung vorbereitet und die beiden Methoden boost.python "bekannt gemacht". Pylog ist der Name der Klasse in Python.

Achtung - dieses Makro muss auf File-globaler Ebene noch vor main() gerufen werden!

R.Wobst, PyCon 2013 Köln 21/44

Page 22: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

4.2. Python-Modul initialisierenif(PyImport_AppendInittab( "CPPlog", &initCPPlog) == -1){ // error}

Das Modul CPPlog wird zum builtin-Modul von Python und kann später

importiert werden, ohne dass ein File CPPlog.py existiert.

Die von BOOST_PYTHON_MODULE implizit definierte Funktion initCPPlog() wird gerufen und damit die Fehlerbehandlung initialisiert.

PyImport_AppendInittab() wird in der Python-C-Api definiert.

R.Wobst, PyCon 2013 Köln 22/44

Page 23: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

4.3. Embedded Python initialisierenPy_Initialize();

Das ist der übliche Start des embedded Interpreters in der Python-C-Api und darf erst jetzt passieren!

Achtung - Py_Finalize() sollte von boost.python aus nicht gerufen werden; ohnehin werden dadurch nicht sicher alle Ressourcen freigegeben (z.B. indirekt von Extensions reservierte).

R.Wobst, PyCon 2013 Köln 23/44

Page 24: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

4.4. Python-Namensraum nach boost.python exportieren

Py::object mainModule = Py::import("__main__");mainNamespace = mainModule.attr("__dict__");

Damit wird der globale Python-Namensraum in C++ bekannt. Wir brauchen ihn im folgenden ständig.

R.Wobst, PyCon 2013 Köln 24/44

Page 25: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

4.5. Klassischer ModulimportPy::object pylog(Py::handle<> \ (PyImport_ImportModule("CPPlog")));mainNamespace["CPPlog"] = pylog;

Wieder wird eine Funktion aus der Python-C-Api gerufen, und der in C++ definierte Python-Modul CPPlog wird unter diesem Namen auch Python bekannt.

R.Wobst, PyCon 2013 Köln 25/44

Page 26: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

4.6. Klasseninstanz exportierenPythonApi::Mylog pylogger = Mylog();scope(pylog).attr("pylogInst") = \ Py::ptr(&pylogger);

Damit wird die C++ - Klasseninstanz pylogger in Python unter dem

Namen CPPlog.pylogInst verfügbar.

R.Wobst, PyCon 2013 Köln 26/44

Page 27: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

4.7. Klasseninstanz in Python verwenden oder erzeugen

#!/usr/bin/env python

CPPlog.pylogInst.write("this is a message")CPPlog.pylogInst.save()

Damit nutzen wir die in C++ definierte Klasseninstanz pylogger, können ihre Methoden rufen und sogar ihre Daten verändern, wie bei einer normalen Python - Klasseninstanz!

Um Klassen zu erzeugen, ruft man den Konstruktor normal auf und könnte dann in C++ per modulname.attr() darauf zugreifen oder einfach die Instanz an eine in C++ bekannten Liste anhängen, vgl.a. 6.2.

R.Wobst, PyCon 2013 Köln 27/44

Page 28: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

4.8. Python-Funktion von C++ aus rufenPy::object pystart = mainNamespace["start"];

Py::list parmlist;parmlist.append(Py::make_tuple(...));...Py:: object testret = pystart(parmlist);

Damit kann man eine Funktion im eingebetteten Interpreter starten, oder - wenn nur ein Skript gestartet werden soll - man verwendet einfach PyRun_SimpleString().

R.Wobst, PyCon 2013 Köln 28/44

Page 29: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

4.9. Die perfekte Lösung?Es geht auch anders, vielleicht sogar einfacher - aber dieses Vorgehen funktionierte in der Praxis und reicht für viele Zwecke aus. Wenn man einenoben beschriebenen Schritt auslässt, gibt es jedenfalls Fehler.

Bei Problemen: Google is your friend ☺

R.Wobst, PyCon 2013 Köln 29/44

Page 30: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

5.Behandlung von Python-FehlernDas bereitet anfangs Jedem arge Kopfzerbrechen, denn es kommt bei boost immer nur die Ausnahme Py::error_already_set an, ohne

Text. Und PyErr_Print() schreibt auf sys.stderr - basta.

Mein Ausweg:

R.Wobst, PyCon 2013 Köln 30/44

Page 31: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

5.1. Ausgaben puffernPyRun_SimpleString( "import sys, cStringIO\n" "sys.stdout = cStringIO.StringIO()\n" "sys.stderr = cStringIO.StringIO()");

Damit werden alle Ausgaben im RAM gepuffert. Das muss aus irgendeinemGrund vor dem Start der Python-Anwendung erfolgen!

R.Wobst, PyCon 2013 Köln 31/44

Page 32: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

5.2. Fehler in boost abfangentry{ // start Python script or call function}catch(Py::error_already_set const&){ ... PyErr_Print();}

Damit landen die Fehlerausgaben in sys.stderr, das gepuffert ist.

R.Wobst, PyCon 2013 Köln 32/44

Page 33: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

5.3. Fehler auswertenPy::object sys = mainNamespace["sys"];Py::object out = sys.attr("stdout");

std::string outTxt = \ Py::extract<std::string>( \ out.attr("getvalue")());

Py::object err = sys.attr("stderr");

std::string errTxt = \ Py::extract<std::string>( \ err.attr("getvalue")());

Mit outTxt und errTxt kann man nun nach Herzenslust verfahren.

R.Wobst, PyCon 2013 Köln 33/44

Page 34: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

Das Verfahren hat natürlich den Nachteil, dass sys.stderr auch noch anderen Text enthalten kann.

Experimente zeigten jedoch, dass die Umleitung von sys.stderr nicht

erst vor dem PyErr_Print() erfolgen darf.

R.Wobst, PyCon 2013 Köln 34/44

Page 35: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

6.Weitere Problemstellungen

6.1. TypechecksWerden Python-Objekte an C++ übergeben, so muss man deren Typ noch mittels der Python-C-Api überprüfen, also etwa mit

int PyInt_Check(PyObject *o)

In boost.python gibt es noch keine entsprechenden Funktionen.

R.Wobst, PyCon 2013 Köln 35/44

Page 36: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

6.2. Funktionen überladen, Konstruktor-Argumente, Standardargumente

Der Aufruf überladener Methoden einer C++ - Klasse ist etwas umständlich.Zunächst muss man sich Hilfsfunktionen in C++ definieren, etwa so:

R.Wobst, PyCon 2013 Köln 36/44

Page 37: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

void WriteNode::write_int(const int value) {valueNode->write(value);}

void WriteNode::write_double( \ const double value) {valueNode->write(value);}

void WriteNode::write_strg( \ const std::string& value) {valueNode->write(value);}

Die Methode valueNode.write() ist überladen.

Danach deklariert man

R.Wobst, PyCon 2013 Köln 37/44

Page 38: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

BOOST_PYTHON_MODULE(WriteNode){ Py::class_<WriteNode>("WriteNode", \ Py::init<std::string>()) .def("write_int", &WriteNode::write_int) .def("write_double", \ &WriteNode::write_double) .def("write_strg", &WriteNode::write_strg);}

Der Konstruktor erhält ein std::string als Argument.

Standardargumente von Funktionen/Methoden müssen analog behandelt werden, da nur Funktionspointer übergeben werden.

R.Wobst, PyCon 2013 Köln 38/44

Page 39: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

6.3. Abbruch des Interpreters von innen oder außen

Der eingebettete Interpreter ist kein gesonderter Prozess. Das heißt:

Ein sys.exit() in Python reißt auch das aufrufende C++ - Programm mit ins Grab!

Aus dem gleichen Grund ist es auch nicht möglich, eine Endlosschleife in Python (oder ein Warten auf ein Ereignis) von C++ aus abzubrechen.

Einziger Ausweg:Programmarchitektur von Anfang an entsprechend planen.

R.Wobst, PyCon 2013 Köln 39/44

Page 40: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

6.4. boost.any vs. Python-ObjekteUm eine Python-Liste mit unterschiedlichen Typen aufzubauen, liegt es nahe, boost::any zu verwenden, denn das ist ja gerade "der variable Typ".

Aber: C++ will alle Typen schon zur Compilezeit wissen, Python erst zur Laufzeit! Man braucht dann Funktionen wie folgende:

R.Wobst, PyCon 2013 Köln 40/44

Page 41: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

Py::object any2pyobj(const boost::any& value){ if(value.type() == typeid(int)) return Py::object(boost::any_cast<int> (value)); else if(value.type() == typeid(double)) return Py::object(boost::any_cast<double> (value)); else if(value.type() == typeid(std::string)) return Py::object( boost::any_cast<std::string>(value)); else // error}

R.Wobst, PyCon 2013 Köln 41/44

Page 42: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

7.AlternativenWenn "nur" Treiber in C/C++ von Python aus verwendet werden müssen, reicht vielleicht das Modul ctypes.

Wenn die Steuerung von C/C++ aus erfolgen muss, reichen vielleicht schon die callback-Funktionen von ctypes (s.dort). So wird z.B. bei Fuse.py verfahren.

Theoretisch leistet die Python-C-Api alles, aber sie ist schwerfällig, und die "manuelle Referenzzählung" ist eine üble Fehlerquelle.

R.Wobst, PyCon 2013 Köln 42/44

Page 43: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

8.Fazitboost.python kann sehr nützlich sein, wenn man die "Umkehrung" des ctypes-Modul braucht, insbesondere C++ - Klasseninstanzen verwenden muss.

Die Schwierigkeiten sind heftig, aber lösbar - dieser Vortrag will eine Hilfe sein.

Meine Erfahrungen aus der Parallelentwicklung in C++ und Python (wie einst schon bei Qt):

erstaunlich, wie viele Laufzeitfehler doch im ach so (typ)sicheren C++ auftreten;

erschreckend, wie aufwändig die Entwicklung in C++ (auch für "geborene C/C++'ler" wie der Autor) erscheint, wenn man parallel dazu das gleiche Problem in Python bearbeitet.

R.Wobst, PyCon 2013 Köln 43/44

Page 44: bo st.python: Nabelschnur zu Python - … · boost.python: Nabelschnur zu Python Ein Erfahrungsbericht mit Rezepten PyCon 2013, Köln Reinhard Wobst. r.wobst@gmx.de R.Wobst, PyCon

R.Wobst, PyCon 2013 Köln 44/44

C++ ist wirklich phantastisch, um als Programmierer seinem Hobby zu frönen und ordentlich Geld zu verdienen. Sprachen wie Python sind nur dazu da, schnell ein Problem gelöst zu bekommen.