Comment répliquer le code Python suivant avec l'API Python C?Comment créer un générateur/itérateur avec l'API Python C?
class Sequence():
def __init__(self, max):
self.max = max
def data(self):
i = 0
while i < self.max:
yield i
i += 1
Jusqu'à présent, j'ai ceci:
#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? */
}
Mais je ne sais pas où aller. Quelqu'un pourrait-il offrir quelques suggestions?
Modifier
Je suppose que le principal problème que je vais avoir avec ce simule la déclaration yield
. Si je comprends bien, c'est une déclaration assez simple, mais en réalité, complexe, il crée un générateur avec ses propres méthodes qui sont appelées automatiquement __iter__()
et next()
. En cherchant dans les documents, il semble être associé à PyGenObject; cependant, comment créer une nouvelle instance de cet objet n'est pas clair. PyGen_New()
prend comme argument un PyFrameObject
, la seule référence à laquelle je peux trouver est PyEval_GetFrame()
, ce qui ne semble pas être ce que je veux (ou est-ce que je me trompe?). Est-ce que quelqu'un a une expérience avec ce qu'ils peuvent partager?
De plus Modifier
Je trouve que cela soit plus clair quand je (essentiellement) a élargi ce que Python a été fait dans les coulisses:
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)
Techniquement, la séquence est désactivée par une, mais vous voyez l'idée. Le seul problème est qu'il est très ennuyeux de créer un nouvel objet chaque fois que l'on a besoin d'un générateur - et encore plus en Python que C en raison de la monstruosité requise qui vient avec la définition d'un nouveau type. Et il ne peut y avoir d'instruction yield
en C car C n'a pas de fermetures. Ce que j'ai fait à la place (puisque je ne pouvais pas le trouver dans l'API Python - s'il vous plaît pointez moi sur un objet standard s'il existe déjà!) Était de créer une classe d'objets générateur générique simple qui rappelait une fonction C pour chaque next()
appel de méthode. Ici, il est (notez que je ne l'ai pas encore essayé la compilation car il n'est pas complète - voir ci-dessous):
#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++);
}
}
Cependant, malheureusement, je ne suis toujours pas fini. La seule question qui me reste est: Comment puis-je déclencher une exception StopIteration
avec l'API C? Je n'arrive pas à le trouver dans le Standard Exceptions. Aussi, peut-être plus important encore, est-ce la bonne façon d'aborder ce problème?
Merci à tous ceux qui suivent encore cela.
Vous savez que cela est 'xrange (max)'? –
Oui, mais ce n'est qu'un exemple simple. J'ai un usage pratique pour ça. – Michael