Invocando Python desde C++

En el argot de Python, invocar código Python desde otro lenguaje se llama "empotrar" (embed) Python.

Con libpython

Aunque el ejemplo que vemos a continuación es extremadamente rudimentario, la librería libpython permite realizar cualquier cualquier módulo Python y ejecutar cualquier estructura del lenguaje. Sin embargo, la interacción entre ambos lenguajes resulta compleja y exenta de todo soporte de orientación a objetos, dado que es una librería C.


In [1]:
!mkdir -p bindings/embed/libpython

In [4]:
%%file bindings/embed/libpython/version.cc

#include <Python.h>

int main(int argc, char *argv[]) {
  Py_Initialize();
  PyRun_SimpleString("import sys; print('{}'.format(sys.version_info[:2]))\n");
  Py_Finalize();
  return 0; 
}


Overwriting bindings/embed/libpython/version.cc

In [5]:
%%file bindings/embed/libpython/Makefile
#!/usr/bin/make -f
# -*- mode:makefile -*-

CXXFLAGS = -I/usr/include -I/usr/include/python2.7/
LDFLAGS = -lpython2.7
TARGET = $(patsubst %.cc, %, $(wildcard *.cc))

all: $(TARGET)

clean:
	$(RM) $(TARGET) *.o *~


Overwriting bindings/embed/libpython/Makefile

In [6]:
!make -C bindings/embed/libpython/ clean all


make: Entering directory '/home/david/repos/python-intro/bindings/embed/libpython'
rm -f  version *.o *~
g++ -I/usr/include -I/usr/include/python2.7/  -lpython2.7  version.cc   -o version
make: Leaving directory '/home/david/repos/python-intro/bindings/embed/libpython'

In [7]:
!./bindings/embed/libpython/version


(2, 7)

Con boost


In [9]:
!mkdir -p bindings/embed/boost

In [10]:
%%file bindings/embed/boost/version.cc
#include <boost/python.hpp>
#include <boost/python/import.hpp>
#include <iostream>

using namespace boost::python;
using namespace std;

int main(int argc, char *argv[]) {
  Py_Initialize();

  PyRun_SimpleString("import sys; result = sys.version_info[:2]");
  object mainobj = import("__main__");
  object dictionary = mainobj.attr("__dict__");
  object result = dictionary["result"];

  tuple tup = extract<tuple>(result);
  if (!extract<int>(tup[0]).check() || !extract<int>(tup[1]).check())
    return 0; 

  int major = extract<int>(tup[0]);
  int minor = extract<int>(tup[1]);

  cout << major << "." << minor << endl;

  Py_Finalize();
  return 0; 
}


Overwriting bindings/embed/boost/version.cc

In [11]:
%%file bindings/embed/boost/Makefile
#!/usr/bin/make -f
# -*- mode:makefile -*-

CXXFLAGS = -Wall -g -I/usr/include -I/usr/include/python2.7/
LDFLAGS = -lpython2.7 -lboost_python-py27
TARGET = $(patsubst %.cc, %, $(wildcard *.cc))

all: $(TARGET)

clean:
	$(RM) $(TARGET) *.o *~


Overwriting bindings/embed/boost/Makefile

In [12]:
!make -C bindings/embed/boost clean all


make: Entering directory '/home/david/repos/python-intro/bindings/embed/boost'
rm -f  version *.o *~
g++ -Wall -g -I/usr/include -I/usr/include/python2.7/  -lpython2.7 -lboost_python-py27  version.cc   -o version
make: Leaving directory '/home/david/repos/python-intro/bindings/embed/boost'

In [13]:
!./bindings/embed/boost/version


2.7

Invocando C++ desde Python

Cuando desde un programa escrito en Python podemos llamar a código escrito en otro lenguaje (mayoritariamente C y C++) se dice que "extendemos" Python.

Con libpython


In [14]:
!mkdir -p ./bindings/extend/libpython/

In [15]:
%%file ./bindings/extend/libpython/greet.c
#include <Python.h>

static PyObject* greet(PyObject* self, PyObject* args) {
  char* msg = "Hello World (libpython)";
  return Py_BuildValue("s", msg);
}

static PyMethodDef function_binder[] = {
  {"greet", greet, METH_VARARGS},
  {NULL, NULL}
};

void initgreet() {
    Py_InitModule("greet", function_binder);
}


Overwriting ./bindings/extend/libpython/greet.c

In [16]:
%%file ./bindings/extend/libpython/Makefile
#!/usr/bin/make -f
# -*- mode:makefile -*-

CFLAGS = -Wall -g -I/usr/include -I/usr/include/python2.7/ -fPIC
LDFLAGS = -lpython2.7 -shared -fPIC
TARGET = $(patsubst %.c, %.so, $(wildcard *.c))

all: $(TARGET)

greet.so: greet.o
	gcc $(LDFLAGS) -o $@ $^

clean:
	$(RM) $(TARGET) *.o *~


Overwriting ./bindings/extend/libpython/Makefile

In [17]:
!make -C ./bindings/extend/libpython clean all


make: Entering directory '/home/david/repos/python-intro/bindings/extend/libpython'
rm -f  greet.so *.o *~
cc -Wall -g -I/usr/include -I/usr/include/python2.7/ -fPIC   -c -o greet.o greet.c
gcc -lpython2.7 -shared -fPIC -o greet.so greet.o
make: Leaving directory '/home/david/repos/python-intro/bindings/extend/libpython'

In [18]:
!rm greet.so
!ln -s bindings/extend/libpython/greet.so .

In [20]:
import greet
greet.greet()


Out[20]:
'Hello World (libpython)'

Con ctypes

ctypes es un módulo de la librería estándar que permite acceder a símbolos de una librería C.


In [1]:
import ctypes

libc = ctypes.cdll.LoadLibrary('libc.so.6')
libc.printf("hola %d\n", 4)


Out[1]:
7

Con boost

(del tutorial de boost-python)


In [2]:
!mkdir -p ./bindings/extend/boost/

In [3]:
%%file ./bindings/extend/boost/greet.cc
#include <boost/python.hpp>

char const* greet() {
  return "Hello World (boost)";
}

BOOST_PYTHON_MODULE(greet) {
  using namespace boost::python;
  def("greet", greet);
}


Overwriting ./bindings/extend/boost/greet.cc

In [5]:
%%file ./bindings/extend/boost/setup.py
from distutils.core import setup, Extension

greet = Extension('greet', sources = ['greet.cc'],
                  libraries = ['boost_python-py27'])

setup(name = 'greet',
      version = '1.0',
      description = 'C++ package for python',
      ext_modules = [greet])


Overwriting ./bindings/extend/boost/setup.py

In [6]:
!cd bindings/extend/boost && python setup.py build


running build
running build_ext
building 'greet' extension
x86_64-linux-gnu-gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I/usr/include/python2.7 -c greet.cc -o build/temp.linux-x86_64-2.7/greet.o
cc1plus: warning: command line option ‘-Wstrict-prototypes’ is valid for C/ObjC but not for C++ [enabled by default]
c++ -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-z,relro -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -D_FORTIFY_SOURCE=2 -g -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-security build/temp.linux-x86_64-2.7/greet.o -lboost_python-py27 -o build/lib.linux-x86_64-2.7/greet.so

In [2]:
!rm greet.so
!ln bindings/extend/boost/build/lib.linux-x86_64-2.7/greet.so .

In [3]:
import greet
greet.greet()


Out[3]:
'Hello World (boost)'

El ejemplo anterior "expone" una función. Por supuesto es posible exponer una clase C++ para ser utilizada desde Python: