Extendiendo/Embebiendo FAQ

¿Puedo crear mis propias funciones en C?

Si, puedes crear módulos incorporados que contengan funciones, variables, excepciones y incluso nuevos tipos en C. Esto esta explicado en el documento Ampliación e incrustación del intérprete de Python.

La mayoría de los libros intermedios o avanzados de Python también tratan este tema.

¿Puedo crear mis propias funciones en C++?

Si, utilizando las características de compatibilidad encontradas en C++. Coloca extern "C" { ... } alrededor los archivos incluidos Python y pon extern "C" antes de cada función que será llamada por el interprete Python. Objetos globales o estáticos C++ con constructores no son una buena idea seguramente.

Escribir en C es difícil; ¿no hay otra alternativa?

Hay un número de alternativas a escribir tus propias extensiones C, dependiendo en que estés tratando de hacer.

Cython y su relativo Pyrex son compiladores que aceptan una forma de Python ligeramente modificada y generan el código C correspondiente. Cython y Pyrex hacen posible escribir una extensión sin tener que aprender la API de Python C.

Si necesitas hacer una interfaz a alguna biblioteca C o C++ que no posee aún extensión Python, puedes intentar empaquetar los tipo de datos de la biblioteca con una herramienta como SWIG. SIP, CXX Boost, o Weave también son alternativas para empaquetar bibliotecas C++.

¿Cómo puedo ejecutar declaraciones arbitrarias de Python desde C?

La función de más alto nivel para hacer esto es PyRun_SimpleString() que toma un solo argumento de cadena de caracteres para ser ejecutado en el contexto del módulo __main__ y retorna 0 si tiene éxito y -1 cuando ocurre una excepción (incluyendo SyntaxError). Si quieres mas control, usa PyRun_String(); mira la fuente para PyRun_SimpleString() en Python/pythonrun.c.

¿Cómo puedo evaluar una expresión arbitraria de Python desde C?

Call the function PyRun_String() from the previous question with the start symbol Py_eval_input; it parses an expression, evaluates it and returns its value.

¿Cómo extraigo valores C de un objeto Python?

Eso depende del tipo de objeto. Si es una tupla, PyTuple_Size() retorna su tamaño, y PyTuple_GetItem() retorna el ítem del índice especificado. Las listas tienen funciones similares, PyListSize() and PyList_GetItem().

Para bytes PyBytes_Size() retorna su tamaño, y PyBytes_AsStringAndSize() proporciona un puntero a su valor y tamaño. Nota que los objetos byte de Python pueden contener bytes null por lo que la función de C strlen() no debe ser utilizada.

Para testear el tipo de un objeto, primero debes estar seguro que no es NULL, y luego usa PyBytes_Check(), PyTuple_Check(), PyList_Check(), etc.

También hay una API de alto nivel para objetos Python que son provistos por la supuestamente llamada interfaz “abstracta” – lee Include/abstract.h para mas detalles. Permite realizar una interfaz con cualquier tipo de secuencia Python usando llamadas como PySequence_Length(), PySequence_GetItem(), etc. así como otros protocolos útiles como números (PyNumber_Index() et al.) y mapeos en las PyMapping APIs.

¿Cómo puedo llamar un método de un objeto desde C?

Se puede utilizar la función PyObject_CallMethod() para llamar a un método arbitrario de un objeto. Los parámetros son el objeto, el nombre del método a llamar, una cadena de caracteres de formato como las usadas con Py_BuildValue(), y los valores de argumento

PyObject *
PyObject_CallMethod(PyObject *object, const char *method_name,
                    const char *arg_format, ...);

Esto funciona para cualquier objeto que tenga métodos – sean estos incorporados o definidos por el usuario. Eres responsable si eventualmente usas Py_DECREF() en el valor de retorno.»

Para llamar, por ejemplo, un método «seek» de un objeto archivo con argumentos 10, 0 (considerando que puntero del objeto archivo es «f»):

res = PyObject_CallMethod(f, "seek", "(ii)", 10, 0);
if (res == NULL) {
        ... an exception occurred ...
}
else {
        Py_DECREF(res);
}

Note que debido a PyObject_CallObject() siempre necesita una tupla para la lista de argumento, para llamar una función sin argumentos, deberás pasar «()» para el formato, y para llamar a una función con un solo argumento, encierra el argumento entre paréntesis, por ejemplo «(i)».

¿Cómo obtengo la salida de PyErr_Print() (o cualquier cosa que se imprime a stdout/stderr)?

En código Python, define un objeto que soporta el método write(). Asigna este objeto a sys.stdout y sys.stderr. Llama a print_error, o solo permite que el mecanismo estándar de rastreo funcione. Luego, la salida se hará cuando invoques write().

La manera mas fácil de hacer esto es usar la clase io.StringIO.

>>> import io, sys
>>> sys.stdout = io.StringIO()
>>> print('foo')
>>> print('hello world!')
>>> sys.stderr.write(sys.stdout.getvalue())
foo
hello world!

Un objeto personalizado para hacer lo mismo se vería así:

>>> import io, sys
>>> class StdoutCatcher(io.TextIOBase):
...     def __init__(self):
...         self.data = []
...     def write(self, stuff):
...         self.data.append(stuff)
...
>>> import sys
>>> sys.stdout = StdoutCatcher()
>>> print('foo')
>>> print('hello world!')
>>> sys.stderr.write(''.join(sys.stdout.data))
foo
hello world!

¿Cómo accedo al módulo escrito en Python desde C?

Puedes obtener un puntero al módulo objeto de esta manera:

module = PyImport_ImportModule("<modulename>");

Si el módulo todavía no se importó, (por ejemplo aún no esta presente en sys.modules), esto inicializa el módulo, de otra forma simplemente retorna el valor de sys.modules["<modulename>"]. Nota que no entra el módulo a ningún espacio de nombres (namespace) –solo asegura que fue inicializado y guardado en sys.modules.

Puedes acceder luego a los atributos del módulo (por ejemplo a cualquier nombre definido en el módulo) de esta forma:

attr = PyObject_GetAttrString(module, "<attrname>");

También funciona llamar a PyObject_SetAttrString() para asignar a variables en el módulo.

¿Cómo hago una interface a objetos C++ desde Python?

Dependiendo de lo que necesites, hay varias maneras de hacerlo. Para hacerlo manualmente empieza por leer the «Extending and Embedding» document.Fíjate que para el sistema de tiempo de ejecución Python, no hay una gran diferencia entre C y C++, por lo que la estrategia de construir un nuevo tipo Python alrededor de una estructura de C de tipo puntero, también funcionará para objetos C++.

Para bibliotecas C++, mira Escribir en C es difícil; ¿no hay otra alternativa?.

He agregado un módulo usando el archivo de configuración y el make falla. ¿Porque?

La configuración debe terminar en una nueva linea, si esta no está, entonces el proceso build falla. (reparar esto requiere algún truco de linea de comandos que puede ser no muy prolijo, y seguramente el error es tan pequeño que no valdrá el esfuerzo.)

¿Cómo puedo depurar una extención?

When using GDB with dynamically loaded extensions, you can’t set a breakpoint in your extension until your extension is loaded.

En tu archivo .gdbinit (o interactivamente), agrega el comando:

br _PyImport_LoadDynamicModule

Luego, cuando corras GDB:

$ gdb /local/bin/python
gdb) run myscript.py
gdb) continue # repeat until your extension is loaded
gdb) finish   # so that your extension is loaded
gdb) br myfunction.c:50
gdb) continue

Quiero compilar un módulo Python en mi sistema Linux, pero me faltan algunos archivos . ¿Por qué?

La mayoría de las versiones empaquetadas de Python no incluyen el directorio /usr/lib/python2.x/config/, que contiene varios archivos que son necesarios para compilar las extensiones Python.

Para Red Hat, instala el python-devel RPM para obtener los archivos necesarios.

Para Debian, corre apt-get install python-dev.

How do I tell «incomplete input» from «invalid input»?

A veces quieres emular el comportamiento del interprete interactivo de Python, que te da una continuación del prompt cuando la entrada esta incompleta (por ejemplo si comenzaste tu instrucción «if» o no cerraste un paréntesis o triples comillas), pero te da un mensaje de error de sintaxis inmediatamente cuando la entrada es invalida.

In Python you can use the codeop module, which approximates the parser’s behavior sufficiently. IDLE uses this, for example.

La manera mas fácil de hacerlo en C es llamar a PyRun_InteractiveLoop() (quizá en un hilo separado) y dejar que el interprete Python gestione la entrada por ti. Puedes también configurar PyOS_ReadlineFunctionPointer() para apuntar a tu función de entrada personalizada.

De todas maneras a veces debes correr el interprete embebido de Python en el mismo hilo que tu api rest, y puedes permitir que PyRun_InteractiveLoop() termine mientras espera por la entrada del usuario. La primera solución es llamar a PyParser_ParseString() y probar con e.error igual a E_EOF, que significa que la entrada esta incompleta. Aquí hay un fragmento de código ejemplo, que no esta probado, inspirado en el código de Alex Farber:

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <node.h>
#include <errcode.h>
#include <grammar.h>
#include <parsetok.h>
#include <compile.h>

int testcomplete(char *code)
  /* code should end in \n */
  /* return -1 for error, 0 for incomplete, 1 for complete */
{
  node *n;
  perrdetail e;

  n = PyParser_ParseString(code, &_PyParser_Grammar,
                           Py_file_input, &e);
  if (n == NULL) {
    if (e.error == E_EOF)
      return 0;
    return -1;
  }

  PyNode_Free(n);
  return 1;
}

Otra solución es intentar compilar la cadena de caracteres recibida con Py_CompileString(). Si compila sin errores, intenta ejecutar el objeto código retornado llamando a PyEval_EvalCode(). De otra manera salva el ingreso para después. Si la compilación falla, encuentra si hay algún o si necesita algún ingreso adicional - extrayendo la cadena de caracteres mensaje de la tupla excepción y comparándola con la cadena de caracteres «unexpected EOF while parsing». Aquí hay un ejemplo completo usando la biblioteca GNU readline (Seguramente querrás ignorar SIGINT mientras llamas a readline()):

#include <stdio.h>
#include <readline.h>

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <object.h>
#include <compile.h>
#include <eval.h>

int main (int argc, char* argv[])
{
  int i, j, done = 0;                          /* lengths of line, code */
  char ps1[] = ">>> ";
  char ps2[] = "... ";
  char *prompt = ps1;
  char *msg, *line, *code = NULL;
  PyObject *src, *glb, *loc;
  PyObject *exc, *val, *trb, *obj, *dum;

  Py_Initialize ();
  loc = PyDict_New ();
  glb = PyDict_New ();
  PyDict_SetItemString (glb, "__builtins__", PyEval_GetBuiltins ());

  while (!done)
  {
    line = readline (prompt);

    if (NULL == line)                          /* Ctrl-D pressed */
    {
      done = 1;
    }
    else
    {
      i = strlen (line);

      if (i > 0)
        add_history (line);                    /* save non-empty lines */

      if (NULL == code)                        /* nothing in code yet */
        j = 0;
      else
        j = strlen (code);

      code = realloc (code, i + j + 2);
      if (NULL == code)                        /* out of memory */
        exit (1);

      if (0 == j)                              /* code was empty, so */
        code[0] = '\0';                        /* keep strncat happy */

      strncat (code, line, i);                 /* append line to code */
      code[i + j] = '\n';                      /* append '\n' to code */
      code[i + j + 1] = '\0';

      src = Py_CompileString (code, "<stdin>", Py_single_input);

      if (NULL != src)                         /* compiled just fine - */
      {
        if (ps1  == prompt ||                  /* ">>> " or */
            '\n' == code[i + j - 1])           /* "... " and double '\n' */
        {                                               /* so execute it */
          dum = PyEval_EvalCode (src, glb, loc);
          Py_XDECREF (dum);
          Py_XDECREF (src);
          free (code);
          code = NULL;
          if (PyErr_Occurred ())
            PyErr_Print ();
          prompt = ps1;
        }
      }                                        /* syntax error or E_EOF? */
      else if (PyErr_ExceptionMatches (PyExc_SyntaxError))
      {
        PyErr_Fetch (&exc, &val, &trb);        /* clears exception! */

        if (PyArg_ParseTuple (val, "sO", &msg, &obj) &&
            !strcmp (msg, "unexpected EOF while parsing")) /* E_EOF */
        {
          Py_XDECREF (exc);
          Py_XDECREF (val);
          Py_XDECREF (trb);
          prompt = ps2;
        }
        else                                   /* some other syntax error */
        {
          PyErr_Restore (exc, val, trb);
          PyErr_Print ();
          free (code);
          code = NULL;
          prompt = ps1;
        }
      }
      else                                     /* some non-syntax error */
      {
        PyErr_Print ();
        free (code);
        code = NULL;
        prompt = ps1;
      }

      free (line);
    }
  }

  Py_XDECREF(glb);
  Py_XDECREF(loc);
  Py_Finalize();
  exit(0);
}

How do I find undefined g++ symbols __builtin_new or __pure_virtual?

Para cargar dinámicamente módulos de extensión g++, debes recompilar Python, hacer un nuevo link usando g++ (cambia LINKCC en el Python Modules Makefile) y enlaza link tu extensión usando g++ (por ejemplo g++ -shared -o mymodule.so mymodule.o`).

¿Puedo crear una clase objeto con algunos métodos implementado en C y otros en Python (por ejemplo a través de la herencia)?

Si, puedes heredar de clases integradas como int, list, dict, etc.

La biblioteca Boost Pyhton (BPL, http://www.boost.org/libs/python/doc/index.html) provee una manera de realizar esto desde C++ (por ejemplo puedes heredar de una clase extensión escrita en C++ usando el BPL).