uso funcion explicacion python c iterator generator python-c-api

funcion - python yield explicacion



¿Cómo crear un generador/iterador con la API de Python C? (2)

¿Cómo replico el siguiente código de Python con la API de Python C?

class Sequence(): def __init__(self, max): self.max = max def data(self): i = 0 while i < self.max: yield i i += 1

Hasta ahora, tengo esto:

#include <Python/Python.h> #include <Python/structmember.h> /* Define a new object class, Sequence. */ typedef struct { PyObject_HEAD size_t max; } SequenceObject; /* Instance variables */ static PyMemberDef Sequence_members[] = { {"max", T_UINT, offsetof(SequenceObject, max), 0, NULL}, {NULL} /* Sentinel */ }; static int Sequence_Init(SequenceObject *self, PyObject *args, PyObject *kwds) { if (!PyArg_ParseTuple(args, "k", &(self->max))) { return -1; } return 0; } static PyObject *Sequence_data(SequenceObject *self, PyObject *args); /* Methods */ static PyMethodDef Sequence_methods[] = { {"data", (PyCFunction)Sequence_data, METH_NOARGS, "sequence.data() -> iterator object/n" "Returns iterator of range [0, sequence.max)."}, {NULL} /* Sentinel */ }; /* Define new object type */ PyTypeObject Sequence_Type = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ "Sequence", /* tp_name */ sizeof(SequenceObject), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* 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, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags*/ "Test generator object", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ Sequence_members, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)Sequence_init, /* tp_init */ 0, /* tp_alloc */ PyType_GenericNew, /* tp_new */ }; static PyObject *Sequence_data(SequenceObject *self, PyObject *args) { /* Now what? */ }

Pero no estoy seguro de a dónde ir después. ¿Alguien podría ofrecer algunas sugerencias?

Editar

Supongo que el problema principal que estoy teniendo con esto es simular la declaración de yield . Tal como lo entiendo, es una afirmación bastante simple, pero en realidad compleja: crea un generador con sus propios __iter__() y next() que se llaman automáticamente. Al buscar en los documentos, parece estar asociado con PyGenObject ; sin embargo, no está claro cómo crear una nueva instancia de este objeto. PyGen_New() toma como argumento un PyFrameObject , la única referencia que puedo encontrar es PyEval_GetFrame() , que no parece ser lo que quiero (¿o estoy equivocado?). ¿Alguien tiene alguna experiencia con esto que puedan compartir?

Editar más

Encontré esto más claro cuando (esencialmente) expandí lo que Python estaba haciendo detrás de las escenas:

class IterObject(): def __init__(self, max): self.max = max def __iter__(self): self.i = 0 return self def next(self): if self.i >= self.max: raise StopIteration self.i += 1 return self.i class Sequence(): def __init__(self, max): self.max = max def data(self): return IterObject(self.max)

Técnicamente, la secuencia está desactivada por uno, pero entiendes la idea.

El único problema con esto es que es muy molesto crear un nuevo objeto cada vez que uno necesita un generador, incluso más en Python que en C debido a la monstruosidad requerida que conlleva la definición de un nuevo tipo. Y no puede haber declaración de yield en C porque C no tiene cierres. Lo que hice en su lugar (ya que no pude encontrarlo en la API de Python, por favor dirígeme a un objeto estándar si ya existe) fue crear una clase de objeto generador simple y genérica que devolvió una función C para cada next() llamada de método Aquí está (tenga en cuenta que aún no he intentado compilar esto porque no está completo, ver abajo):

#include <Python/Python.h> #include <Python/structmember.h> #include <stdlib.h> /* A convenient, generic generator object. */ typedef PyObject *(*callback)(PyObject *callee, void *info) PyGeneratorCallback; typedef struct { PyObject HEAD PyGeneratorCallback callback; PyObject *callee; void *callbackInfo; /* info to be passed along to callback function. */ bool freeInfo; /* true if |callbackInfo| should be free''()d when object * dealloc''s, false if not. */ } GeneratorObject; static PyObject *Generator_iter(PyObject *self, PyObject *args) { Py_INCREF(self); return self; } static PyObject *Generator_next(PyObject *self, PyObject *args) { return self->callback(self->callee, self->callbackInfo); } static PyMethodDef Generator_methods[] = { {"__iter__", (PyCFunction)Generator_iter, METH_NOARGS, NULL}, {"next", (PyCFunction)Generator_next, METH_NOARGS, NULL}, {NULL} /* Sentinel */ }; static void Generator_dealloc(GenericEventObject *self) { if (self->freeInfo && self->callbackInfo != NULL) { free(self->callbackInfo); } self->ob_type->tp_free((PyObject *)self); } PyTypeObject Generator_Type = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ "Generator", /* tp_name */ sizeof(GeneratorObject), /* tp_basicsize */ 0, /* tp_itemsize */ Generator_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, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags*/ 0, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ PyType_GenericNew, /* tp_new */ }; /* Returns a new generator object with the given callback function * and arguments. */ PyObject *Generator_New(PyObject *callee, void *info, bool freeInfo, PyGeneratorCallback callback) { GeneratorObject *generator = (GeneratorObject *)_PyObject_New(&Generator_Type); if (generator == NULL) return NULL; generator->callee = callee; generator->info = info; generator->callback = callback; self->freeInfo = freeInfo; return (PyObject *)generator; } /* End of Generator definition. */ /* Define a new object class, Sequence. */ typedef struct { PyObject_HEAD size_t max; } SequenceObject; /* Instance variables */ static PyMemberDef Sequence_members[] = { {"max", T_UINT, offsetof(SequenceObject, max), 0, NULL}, {NULL} /* Sentinel */ } static int Sequence_Init(SequenceObject *self, PyObject *args, PyObject *kwds) { if (!PyArg_ParseTuple(args, "k", &self->max)) { return -1; } return 0; } static PyObject *Sequence_data(SequenceObject *self, PyObject *args); /* Methods */ static PyMethodDef Sequence_methods[] = { {"data", (PyCFunction)Sequence_data, METH_NOARGS, "sequence.data() -> iterator object/n" "Returns generator of range [0, sequence.max)."}, {NULL} /* Sentinel */ }; /* Define new object type */ PyTypeObject Sequence_Type = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ "Sequence", /* tp_name */ sizeof(SequenceObject), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* 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, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags*/ "Test generator object", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ Sequence_members, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)Sequence_init, /* tp_init */ 0, /* tp_alloc */ PyType_GenericNew, /* tp_new */ }; static PyObject *Sequence_data(SequenceObject *self, PyObject *args) { size_t *info = malloc(sizeof(size_t)); if (info == NULL) return NULL; *info = 0; /* |info| will be free''()d by the returned generator object. */ GeneratorObject *ret = Generator_New(self, info, true, &Sequence_data_next_callback); if (ret == NULL) { free(info); /* Watch out for memory leaks! */ } return ret; } PyObject *Sequence_data_next_callback(PyObject *self, void *info) { size_t i = info; if (i > self->max) { return NULL; /* TODO: How do I raise StopIteration here? I can''t seem to find * a standard exception. */ } else { return Py_BuildValue("k", i++); } }

Sin embargo, desafortunadamente, todavía no he terminado. La única pregunta que me queda es: ¿Cómo StopIteration una excepción StopIteration con la API C? Parece que no puedo encontrarlo listado en las Excepciones Estándar . Además, tal vez lo más importante, ¿es esta la forma correcta de abordar este problema?

Gracias a todos los que todavía siguen esto.


A continuación se muestra una implementación simple del módulo de spam con una función de iterador que devuelve myiter(int) :

import spam for i in spam.myiter(10): print i

imprime números del 0 al 9.

Es más sencillo que su caso, pero muestra los puntos principales: definir el objeto con los __iter__() estándar __iter__() y next() , e implementar el comportamiento del iterador, incluido el aumento de StopIteration cuando corresponda.

En su caso, el objeto del iterador necesita mantener la referencia a la Secuencia (por lo que necesitará un método de desglose para Py_DECREF). La secuencia en sí necesita implementar __iter()__ y crear un iterador dentro de ella.

Estructura que contiene el estado del iterador. (En su versión en lugar de m, tendría referencia a Sequence).

typedef struct { PyObject_HEAD long int m; long int i; } spam_MyIter;

El __iter__() del iterador. Siempre simplemente regresa a self . Permite que el iterador y la colección se traten de la misma manera en construcciones como for ... in ...

PyObject* spam_MyIter_iter(PyObject *self) { Py_INCREF(self); return self; }

Implementación de nuestra iteración: método next() .

PyObject* spam_MyIter_iternext(PyObject *self) { spam_MyIter *p = (spam_MyIter *)self; if (p->i < p->m) { PyObject *tmp = Py_BuildValue("l", p->i); (p->i)++; return tmp; } else { /* Raising of standard StopIteration exception with empty value. */ PyErr_SetNone(PyExc_StopIteration); return NULL; } }

Necesitamos una versión extendida de la estructura PyTypeObject para proporcionar a Python información sobre __iter__() y next() . Queremos que se los llame de manera eficiente, de modo que no se busquen nombres en el diccionario.

static PyTypeObject spam_MyIterType = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "spam._MyIter", /*tp_name*/ sizeof(spam_MyIter), /*tp_basicsize*/ 0, /*tp_itemsize*/ 0, /*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, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_ITER, /* tp_flags: Py_TPFLAGS_HAVE_ITER tells python to use tp_iter and tp_iternext fields. */ "Internal myiter iterator object.", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ spam_MyIter_iter, /* tp_iter: __iter__() method */ spam_MyIter_iternext /* tp_iternext: next() method */ };

myiter(int) crea un iterador.

static PyObject * spam_myiter(PyObject *self, PyObject *args) { long int m; spam_MyIter *p; if (!PyArg_ParseTuple(args, "l", &m)) return NULL; /* I don''t need python callable __init__() method for this iterator, so I''ll simply allocate it as PyObject and initialize it by hand. */ p = PyObject_New(spam_MyIter, &spam_MyIterType); if (!p) return NULL; /* I''m not sure if it''s strictly necessary. */ if (!PyObject_Init((PyObject *)p, &spam_MyIterType)) { Py_DECREF(p); return NULL; } p->m = m; p->i = 0; return (PyObject *)p; }

El resto es bastante aburrido ...

static PyMethodDef SpamMethods[] = { {"myiter", spam_myiter, METH_VARARGS, "Iterate from i=0 while i<m."}, {NULL, NULL, 0, NULL} /* Sentinel */ }; PyMODINIT_FUNC initspam(void) { PyObject* m; spam_MyIterType.tp_new = PyType_GenericNew; if (PyType_Ready(&spam_MyIterType) < 0) return; m = Py_InitModule("spam", SpamMethods); Py_INCREF(&spam_MyIterType); PyModule_AddObject(m, "_MyIter", (PyObject *)&spam_MyIterType); }


En Sequence_data , debe devolver una nueva instancia de PyInt o lanzar una excepción de StopIteration que le indique al código que no hay más valores. Ver PEP 255 para más detalles y 9.10 Generadores .

Consulte el Protocolo del iterador para las funciones de ayuda en la API de Python / C.