Módulos en C para Python II: Objetos

En algunas ocasiones implementando extensiones para Python se hace necesario proveer de objetos con sus respectivos atributos en vez de funciones accesibles directamente a través del módulo. Para este ejemplo se emplearán figuras geométricas. Lo primero es crear las estructuras de datos involucradas.

#include 

staticforward PyTypeObject figures_TriangleType;

typedef struct {
    PyObject_HEAD
    float a,b,c;
    char *name;
} figures_TriangleObject;

La sentencia staticforward permitirá acceder a la estructura del tipo Python de Triangle aunque no se haya declarado, aunque no sea muy ortodoxo es necesario ya que no se dispondrá de las funciones y datos para componerlas hasta las últimas lineas del módulo.

No se debe confundir TriangleType con TriangleObject, la primera será la estructura que utilizará Python para saber que métodos y atributos pertenecen al objeto y en definitiva hacer que este se comporte como tal, en cambio es en la estructura TriangleObject donde se encuentran los datos que se instanciarán con el propio objeto durante la ejecución.

A continuación se define la función de construcción del objeto, reservando memoria para la estructura de datos que le corresponde, la función encargada de ello es tp_alloc() que está incluida en la estructura del TypeObject. Una vez creado el objeto se devuelve.

static PyObject*
figures_triangle_new(PyTypeObject *type, PyObject *args){
	figures_TriangleObject *self;

	self = (figures_TriangleObject*)type->tp_alloc(type,0);
	return (PyObject *) self;
}

Es en la función de inicialización cuando se procede al análisis de los parámetros (arg) facilitados. En este caso se espera que se faciliten una cadena seguida de tres decimales, el formato que utiliza esta función es muy parecido al que emplean otras como printf().

static int
figures_triangle_init(PyObject *self, PyObject *args) {
	char *name;
	float a,b,c;
	figures_TriangleObject* triangle;

	if (!PyArg_ParseTuple(args, "sfff", &name, &a,&b,&c))
        {
		PyErr_SetString( PyExc_TypeError,
			"Parameters: name string, a, b and c lenght"
		);
		return -1;
	}
	if (self != NULL) {
		triangle = (figures_TriangleObject*) self;
	} else {
		triangle = PyObject_New(figures_TriangleObject, &figures_TriangleType);
	}
	triangle->a = a ;
	triangle->b = b ;
	triangle->c = c ;
	triangle->name = name;
	return 0 ;
}

Debe tenerse en cuenta de que si el patrón esperado para los parámetros no coincide con los que se facilitan se emitirá un mensaje de error informando del suceso. Esta función devolverá 0 si el proceso de la inicialización ha funcionado correctamente, en caso contrario un entero negativo.

También es posible que el puntero a self sea nulo, en tal caso esta función será la encargada de invocar a la rutina que reserve memoria, en cualquier caso se proseguirá asignando los valores recibidos en los parámetros a las variables del objeto.

La función anterior es invocada desde una función de creación, que controla que esta se ha realizado correctamente y en caso contrario devolver un objeto nulo (None).

static PyObject*
figures_triangle_create(PyObject *self, PyObject *args){
	figures_TriangleObject *new; 

	figures_TriangleType.tp_new = PyType_GenericNew;
	PyType_Ready(&figures_TriangleType);
	new = PyObject_New(figures_TriangleObject, &figures_TriangleType);
	if (figures_triangle_init( (PyObject *)new , args) == 0)
		return (PyObject *)new;
	else
		return Py_None;
}

Para crear el objeto debe implementarse la función correspondiente, será esta la que se llamará cuando se haga referencia en Python a figures.Triangle().

La complejidad del destructor de datos dependerá de la estructura propia del objeto, para el caso del triangulo es muy simple.

static void
figures_triangle_dealloc(figures_TriangleObject* self)
{
	self->ob_type->tp_free((PyObject*)self);
}

La estructura TriangleObject incorpora como todo PyObject punteros a funciones para gestionarlos como objetos, una de estas es precisamente la rutina para dejar de reservar la memoria ocupada por la estructura del PyObject.

El primer método de este objeto servirá para calcular el área, debe tenerse en cuenta que Python debe obtener al final de la función un objeto válido, para eso se emplea la función Py_BuildValue().

static PyObject*
figures_triangle_area(figures_TriangleObject *self, PyObject *value, void *closure){
	float area;

	area  = self->a * self->b / 2;
	return (Py_BuildValue("f",area));
}

Py_BuildValue() utiliza las mismas normas de formato que PyArg_ParseTuple().

Para acceder a los atributos del objeto deben programarse las respectivas funciones get() y set(), a se implementa método get() para recuperar la longitud del lado A del triángulo.

static PyObject*
figures_triangle_get_a(figures_TriangleObject *self, void* p )
{
    return Py_BuildValue("f", self->a);
}

El método set() es algo mas complicado ya que tenemos que debe confirmase que facilita un parámetro y que además este es del tipo float.

static int
figures_triangle_set_a(figures_TriangleObject *self, PyObject *value, void *closure)
{
	float a;

	if (value == NULL) {
		PyErr_SetString(PyExc_TypeError,
			"Cannot delete the first attribute");
		return -1;
	}
	if (PyArg_Parse(value,"f", &a )) self->a = a ;
	else {
	 	PyErr_SetString(PyExc_TypeError,
			"The first attribute value must be float");
		return -1;
	}
	Py_INCREF(value);
	return 0;
}

Las descripciones de getter y setter son necesarias para que python sepa que atributos puede manejar y a que métodos corresponde. Cada registro debe incluir:

  • El nombre del atributo en una cadena.
  • Un puntero a la función para el método get a al que debe preceder un cast (getter).
  • Un puntero a la función para el método set a al que debe preceder un cast (setter).
  • Una cadena con una breve descripción del atributo.
  • Un puntero a datos opcionales que se hará llegar a las anteriores funciones a través del parámetro closure, en la mayoría de los casos será nulo.

El último registro de esta estructura contendrá un único valor NULL.

Para más detalles sobre las estructuras que emplean las extensiones Python en C existe documentación completa.

static PyGetSetDef figures_triangle_getset[] = {
	{ "a", (getter)figures_triangle_get_a,
		(setter)figures_triangle_set_a,
		"a lenght", NULL },
	{ NULL }
};

También deben declararse los métodos en una estructura PyMethodDef que al igual que la responsable de los atributos se incluirá en la estructura del tipo Python para Triangle. Cada método debe tener un registro compuesto por:

  • El nombre del atributo en una cadena.
  • Un puntero a la función haciendo un cast como (PyCFunction).
  • Un flag que indicará si deben proporcionarse parámetros al método, la alternativa es METH_VARARGS.
  • Por último una breve descripción del método.
static PyMethodDef figures_triangle_methods[] = {
	{ "area", (PyCFunction)figures_triangle_area,
		METH_NOARGS , "calculates area"},
	{NULL, NULL, 0, NULL}
};

De la misma manera se declaran los métodos para el propio módulo, en este ejemplo se proporcionará la función constructora de Triangle.

static PyMethodDef figures_methods[] = {
	{"Triangle", (PyCFunction)figures_triangle_create, METH_VARARGS,
		"Create a new Triangle object."},
	{NULL, NULL, 0, NULL}
};

Para rellenar la estructura PyTypeObject deben utilizarse, al igual que en las estructuras anteriores, casts para las funciones con el propósito de evitar warnings.

static PyTypeObject figures_TriangleType = {
    PyObject_HEAD_INIT(NULL)
    0,
    "figures.Triangle",
    sizeof(figures_TriangleObject),
    0,
    (destructor)figures_triangle_dealloc, /*tp_dealloc*/
    0,          /*tp_print*/
    0,          /*tp_getattr*/
    0,          /*tp_setattr*/
    0,          /*tp_compare*/
    0,          /*tp_repr*/
    0,          /*tp_as_number*/
    0,          /*tp_as_sequence*/
    0,          /*tp_as_mapping*/
    0,          /*tp_hash */
    0,0,0,
    0,0,
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    "Triangle objects",
    0,0,0,0,0,0,
    figures_triangle_methods,
    0,
    figures_triangle_getset,
    0,0,0,0,0,
    (initproc)figures_triangle_init,
    0,
    (newfunc)figures_triangle_new,
};

DL_EXPORT(void)

Por último el inicializador del módulo indicará que estructura contiene los métodos de este.

initfigures(void){
    PyObject* m;
    figures_TriangleType.ob_type = &PyType_Type;
    m = Py_InitModule("figures", figures_methods);
}

Para compilar el módulo debe ejecutarse el siguiente script Python:

from distutils.core import setup, Extension
setup(name = "figures", version = "1.0",
    ext_modules = [Extension("figures", ["figures.c"],
    library_dirs = [ './' ] ,
    )]
)

Cabe recordar que si se necesita incluir librerias opcionales puede añadirse a los parámetros de Extension() la tupla:

extra_objects = ["libreria.a"]

Para utilizar la libreria tendreis que copiar el fichero figures.so desde el directorio que distutils ha creado dentro de build al directorio actual.

0 Comentarios

  1. Hello, just wanted to say, I enjoyed this article. It was practical. Keep on posting!
    n級品おすすめ

  2. Tremendous issues here. I am very glad to look your post. Thank you a lot and I’m taking a look forward to touch you. Will you please drop me a mail?
    ブランド コピー 国内発送 http://www.oumma.be/forum-annonce-oumma

  3. I got this web page from my buddy who informed me about this website and now this time I am browsing this site and reading very informative articles or reviews at this time.
    スーパーコピー財布買ってみた http://www.oumma.be/?start=10

  4. スーパーコピー時計優良店

    Hey! Someone in my Facebook group shared this website with us so I came to give it a look. I’m definitely enjoying the information. I’m book-marking and will be tweeting this to my followers! Great blog and fantastic design and style.
    スーパーコピー時計優良店 https://www.hsq.de/index.php/kontakt

  5. ブランドコピー激安

    I’ve been surfing online greater than 3 hours lately, yet I by no means discovered any interesting article like yours. It is beautiful price sufficient for me. In my opinion, if all website owners and bloggers made just right content as you probably did, the net might be a lot more useful than ever before.
    ブランドコピー激安 http://lvi.rtu.lv/projekti

  6. スーパーコピー

    Ahaa, its pleasant conversation concerning this paragraph at this place at this blog, I have read all that, so now me also commenting at this place.
    スーパーコピー https://www.coopvicinatolombardia.it/attivati.html

  7. milad

    very good website

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *