Python erweitern in C und C++
Robert Franke
DESY Zeuthen und Humboldt Universität Berlin Berlin, 18.02.2010
Inhalt
1 Einführung 2 Ctypes 3 Python API 4 Cython 5 SWIG 6 Boost.PythonEinführung
Warum C/C++?
Performance
Wrappen existierender Bibliotheken Verteilen von proprietärem Code
Ctypes
Was ist ctypes
einfachstes Interface zu C-Bibliotheken in Standard Library seit Python 2.5 Basiert auf libffi für call conventions
keine Änderung/Ergänzung der C-Bibliothek nötig! Automatische Typumwandlung von Argumenten Unterstützt Pointer, Structs, Unions
Ctypes
Wrappen von sqrt aus libm.so
importctypes
importmath
libm=ctypes.cdll.LoadLibrary(’libm.so’)
sqrt=libm.sqrt
sqrt.restype=ctypes.c_double sqrt.argtypes= [ctypes.c_double] printsqrt(3.2),math.sqrt(3.2)
Ctypes
Arbeiten mit Structs
#include<stdlib.h> structdoublearray{
unsigned long longlength;
double*array; };
doublesum(structdoublearray*arr){
doubles=0.;
unsigned long longi;
for(i=0;i<arr->length;i++){ s+=arr->array[i]; }
returns; }
Ctypes
importctypes importrandom
classDOUBLEARRAY(ctypes.Structure):
_fields_= [("length",ctypes.c_ulonglong),
("array",ctypes.POINTER(ctypes.c_double))]
libdoublearray=ctypes.cdll.LoadLibrary("./doublearray.so")
libdoublearray.sum.argtypes= [ctypes.POINTER(DOUBLEARRAY)]
libdoublearray.sum.restype=ctypes.c_double doublearray=ctypes.c_double*100
array=doublearray()
foriinxrange(0,100):
array[i] =random.random()
carray=DOUBLEARRAY(100,array)
Python API
Die Python API
Low-Level Interface
Manuelles Reference Counting Wenig Overhead
Python API
Kubikwurzel
#include<Python.h>
#include<math.h> staticPyObject*ErrorObject;
staticPyObject*cubic_root(PyObject*self,PyObject*args){
doubleguess=1.0;
doubleN;
if(!PyArg_ParseTuple(args,"d", &N))
return0;
while(fabs((guess*guess*guess-N)/N) >1e-8){ guess=1./3* (2*guess+N/(guess*guess)); }
returnPy_BuildValue("d",guess); }
Python API
Kubikwurzel (2)
staticcharcubic_root__doc__[] ="Approximate the cubic root with the Babylonian method";
staticPyMethodDefcubicmethods[] ={
{"cubic_root",cubic_root,METH_VARARGS,cubic_root__doc__}, {NULL,NULL,0,NULL}
};
voidinitcubic(void){
PyObject*m, *d;
m=Py_InitModule("cubic",cubicmethods); d=PyModule_GetDict(m);
ErrorObject=Py_BuildValue("s","cubic module error"); PyDict_SetItemString(d,"error",ErrorObject);
if(PyErr_Occurred())
Py_FatalError("Can’t initialize module cubic!"); }
Python API
setup.py
fromdistutils.coreimportsetup,Extension setup(name="cubic",
version="1.0",
maintainer="Robert Franke",
maintainer_email="[email protected]",
description="Sample, simple Python extension module",
ext_modules= [Extension(’cubic’,sources=[’cubic.c’])] )
Python API
Eine Liste von Zufallszahlen
#include<Python.h>
#include<stdlib.h>
#include<time.h>
staticPyObject*ErrorObject;
staticPyObject*random_list(PyObject*self,PyObject*args){
longnrand,i;
doubled;
if(!PyArg_ParseTuple(args,"l", &nrand))
return0;
PyObject*list=PyList_New(nrand);
for(i=0;i<nrand;i++){
d=drand48();
PyObject*listitem=Py_BuildValue("d",d);
PyList_SET_ITEM(list,i,listitem); }
returnlist; }
staticPyObject*wrap_drand48(PyObject*self,PyObject*args){
doubled;
Python API
Eine Liste von Zufallszahlen (2)
staticcharwrap_drand__doc__[] ="A wrapper around the libc drand48 function";
staticcharrandom_list__doc__[] ="Generate a list of random numbers";
staticPyMethodDefrandommethods[] ={
{"random",wrap_drand48,METH_VARARGS,wrap_drand__doc__}, {"random_list",random_list,METH_VARARGS,random_list__doc__}, {NULL,NULL,0,NULL}
};
voidinitmyrandom(void){
PyObject*m, *d;
m=Py_InitModule("myrandom",randommethods); d=PyModule_GetDict(m);
ErrorObject=Py_BuildValue("s","myrandom module error"); PyDict_SetItemString(d,"error",ErrorObject);
if(PyErr_Occurred())
Py_FatalError("Can’t initialize module myrandom!"); srand48(time(0));
Python API
Reference counting
Python nutzt reference counting zum Speichermanagement Owned reference vs. borrowed reference
Py_INCREF: erhöht reference count
Py_DECREF: erniedrigt reference count, löscht Objekt wenn
count==0
die meisten API Funktionen rufen INCREF für die Objekt , die sie zurückgeben
Ausnamhme u.a.: PyListi_GetItem, PyTuple_GetItem Quelle von Crashs und Memory Leaks!
Python API
Shallow list copy
#include<Python.h>
#include<stdlib.h>
staticPyObject*ErrorObject;
staticPyObject*copy_list(PyObject*self,PyObject*args){ Py_ssize_tlen,i;
PyObject*list;
PyArg_ParseTuple(args,"O",&list);
if(!PyList_Check(list)){
PyErr_SetString(PyExc_TypeError,"expected list type");
returnNULL; }
len=PyList_Size(list);
PyObject*new_list=PyList_New(len);
for(i=0;i<len;i++){
PyObject*listitem=PyList_GetItem(list,i);
Py_INCREF(listitem);//Important as PyList_GetItem does not //increment the refcount!
PyList_SET_ITEM(new_list,i,listitem); }
returnnew_list; }
Python API
Shallow list copy (2)
staticcharcopy_list__doc__[] ="Make a shallow list copy";
staticPyMethodDefcopy_listmethods[] ={
{"copy",copy_list,METH_VARARGS,copy_list__doc__}, {NULL,NULL,0,NULL}
};
voidinitcopy_list(void){ PyObject*m, *d;
m=Py_InitModule("copy_list",copy_listmethods);
d=PyModule_GetDict(m);
ErrorObject=Py_BuildValue("s","copy_list module error");
PyDict_SetItemString(d,"error",ErrorObject);
if(PyErr_Occurred())
Py_FatalError("Can’t initialize module copy_list!"); }
Cython
Was ist Cython?
Python mit Typen vorher: Pyrex
Methoden werden mit Typdeklarationen angereichert Cython macht daraus C-Code, den man dann kompiliert
Cython
Kubikwurzel
frommathimportfabs
defcubic_root(double f):
cdef double x=1.5# Start value
whilefabs((x*x*x-f)/f) >1e-8:
x= (2*x+f/(x*x))/3.
SWIG
Was ist SWIG?
Simplified Wrapper and Interface Generator
Wrappergenerator für viele Sprachen: Python, Perl, Ruby, TCL, Common List, C#, etc.
Unterstützt C und C++ Definitionen in Interface-Files
Oft können Header direkt als Interface Deklarationen benutzt werden
SWIG
Kubikwurzel
%module cubic
%{
externdoublecubic_root(double);
%}
Boost.Python
Was ist Boost.Python
Teil der Boost C++-Bibliothek
Entwickelt von Dave Abrahams et al.
Aktuell: Boost 1.42.0, Version 2 von Boost.Python
Unterstützt Funktionen, Memberfunktionen, Klassenfunktionen, Docstrings, Properties, Overloading, Keywordargumente, Defaultargumente, Serialization etc.
Boost.Python
Kubikwurzel
#include<boost/python.hpp>
#include<cmath>
using namespaceboost::python;
doublecubic_root(constdoubleN){
doubleguess=1.0;
while(std::fabs((guess*guess*guess-N)/N) >1e-8){ guess=1./3* (2*guess+N/(guess*guess));
}
returnguess;
}
BOOST_PYTHON_MODULE(cubic){ def("cubic_root",&cubic_root);
Boost.Python Ein Wrapper um boost::gil
#include<stdexcept>
#include<boost/filesystem.hpp> // includes all needed Boost.Filesystem declarations
#include"image.h"
using namespaceboost::gil;
using namespaceboost::filesystem;
using namespacestd;
usingboost::shared_ptr;
Image::Image(conststd::string&path){
if(!exists(path)){
throwstd::runtime_error("File does not exist"); }
jpeg_read_image(path,img_);
img_view_=view(img_); }
shared_ptr<Pixel>Image::at(constintx,constinty)const{
rgb8c_view_t::iteratorit=img_view_.at(x,y);
Pixel*px=newPixel((*it)[0],(*it)[1],(*it)[2]);
returnshared_ptr<Pixel>(px); }
unsigned intImage::width()const{
return static_cast<unsigned int>(img_view_.width()); }
Boost.Python
Ein Wrapper um boost::gil (2)
#include<boost/python.hpp>
#include<string>
#include"image.h"
using namespaceboost::python;
usingboost::shared_ptr;
usingstd::string;
BOOST_PYTHON_MODULE(pyimage){
class_<Pixel,shared_ptr<Pixel> >("Pixel",init<short,short,short>()) .def_readwrite("red",&Pixel::r_)
.def_readwrite("green",&Pixel::g_) .def_readwrite("blue",&Pixel::b_) ;
class_<Image>("Image",init<string>()) .def("width",&Image::width) .def("height",&Image::height) .def("at",&Image::at) ;
Boost.Python
Callbacks in C++ und Python
#include"callback.h"
#include<boost/foreach.hpp>
doublemapsum(conststd::vector<double>&vec,map_func_typefunc){
doublesum=0;
BOOST_FOREACH(doubled,vec){ sum+=func(d);
}
returnsum;
}
doublesquare(constdoubled){
returnd*d;
Boost.Python
Callbacks in C++ und Python (2)
#include<boost/python.hpp>
#include<boost/python/suite/indexing/vector_indexing_suite.hpp>
#include"callback.h"
#include"py_boost_function.hpp"
#include"container_conversions.h"
map_func_typesquare_ptr(square);
using namespaceboost::python;
using namespacescitbx::boost_python::container_conversions;
BOOST_PYTHON_MODULE(callback){
boost::python::class_<std::vector<double>,boost::shared_ptr<std::vector<double> > >("PyVec") .def(boost::python::vector_indexing_suite<std::vector<double> >());
from_python_sequence<std::vector<double>,variable_capacity_policy>();
def("reduce",&mapsum);
def_function<double(constdouble)>(
"map_func_t",
"A function"
);
scope().attr("square") =square_ptr; }
Boost.Python
Dokumentation und Links
http://docs.python.org/c-api/ http://docs.python.org/library/ctypes.html http://www.swig.org/doc.html http://www.boost.org/doc/libs/1_42_0/libs/ python/doc/index.html http://wiki.python.org/moin/boost.python/HowTo http://edcjones.tripod.com/refcount.html