boost.python: Nabelschnur zu Python

Werbung
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
1. Was ist embedded Python?
Hat nichts mit embedded Software zu tun, sondern ist ein PythonInterpreter, der von einer C-Schnittstelle aus gesteuert wird.
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-Befehl
27
# Ausgabe
R.Wobst, PyCon 2013 Köln
2/44
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
Ein Aufruf von reverse() dreht die aktuelle Zeile (in der der Cursor steht) um
- in vim:
$ vim _textfile_
...
:py execfile('vimdemo.py')
:reverse()
"define 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
R.Wobst, PyCon 2013 Köln
5/44
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
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
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
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
3. boost.python
boost ist eine Sammlung leistungsfähiger C++ - Bibliotheken (Release 1.52
enthä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 PythonC-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 Python
Hier wird nur auf die Anbindung an ein embedded Python eingegangen.
R.Wobst, PyCon 2013 Köln
10/44
3.1.
Fähigkeiten
einfacheres Interface als bei Python-C-API
BEISPIEL: Funktionalität des Python-Skripts
import random
print random.random()
nachbilden:
R.Wobst, PyCon 2013 Köln
11/44
#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
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
3.2.
Probleme
Mangelhafte, 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
/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
/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
Templates sind zwar typsicher, aber der Programmierer weiß trotzdem
meist nicht, was hinter den Kulissen passiert - Nebeneffekte machen den
Nutzen 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
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
Fazit: Wenn man einen Modulnamen WriteNode angibt, wird impliziert
eine neue Funktion initWriteNode() definiert, die später verwendet
werden 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 PythonProgrammierer auf nervige Längen. Kein Wunder:
Der Präprozesser generiert aus #include <iostream> etwa
437 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
4. Praktisches Beispiel
Wir 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
4.1.
Python-Modul definieren
namespace 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
4.2.
Python-Modul initialisieren
if(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
4.3.
Embedded Python initialisieren
Py_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
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
4.5.
Klassischer Modulimport
Py::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
4.6.
Klasseninstanz exportieren
PythonApi::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
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
4.8.
Python-Funktion von C++ aus rufen
Py::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
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 einen
oben beschriebenen Schritt auslässt, gibt es jedenfalls Fehler.
Bei Problemen: Google is your friend ☺
R.Wobst, PyCon 2013 Köln
29/44
5. Behandlung von Python-Fehlern
Das 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
5.1.
Ausgaben puffern
PyRun_SimpleString(
"import sys, cStringIO\n"
"sys.stdout = cStringIO.StringIO()\n"
"sys.stderr = cStringIO.StringIO()");
Damit werden alle Ausgaben im RAM gepuffert. Das muss aus irgendeinem
Grund vor dem Start der Python-Anwendung erfolgen!
R.Wobst, PyCon 2013 Köln
31/44
5.2.
Fehler in boost abfangen
try
{
// 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
5.3.
Fehler auswerten
Py::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
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
6. Weitere Problemstellungen
6.1.
Typechecks
Werden 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
6.2. Funktionen überladen, KonstruktorArgumente, 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
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
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
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
6.4.
boost.any vs. Python-Objekte
Um 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
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
7. Alternativen
Wenn "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
8. Fazit
boost.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
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.
R.Wobst, PyCon 2013 Köln
44/44
Herunterladen