2009-11-29 8 views
38

Wie repliziere ich den folgenden Python-Code mit der Python C-API?Wie erstelle ich einen Generator/Iterator mit der Python C API?

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

Bisher habe ich dies:

#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? */ 
} 

Aber ich bin nicht sicher, wohin zu gehen. Kann mir jemand Vorschläge machen?

bearbeiten

Ich nehme an, das Hauptproblem ich mit diesem habe die yield Anweisung simuliert. Wie ich es verstehe, ist es eine ziemlich einfach aussehende, aber in Wirklichkeit komplexe Aussage - es erzeugt einen Generator mit seinen eigenen __iter__() und next() Methoden, die automatisch aufgerufen werden. Beim Durchsuchen der Dokumente scheint es mit der PyGenObject verknüpft zu sein; Es ist jedoch unklar, wie eine neue Instanz dieses Objekts erstellt wird. PyGen_New() nimmt als Argument eine PyFrameObject, die einzige Referenz, auf die ich finden kann, ist PyEval_GetFrame(), die nicht zu sein scheint, was ich will (oder irre ich mich?). Hat jemand Erfahrung damit, die sie teilen können?

Weitere bearbeiten

fand ich dies deutlicher zu sein, wenn ich (im Wesentlichen) erweitert, was Python hinter den Kulissen tat:

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) 

Technisch die Reihenfolge aus, indem man ist, aber Sie bekommen die Idee.

Das einzige Problem dabei ist, dass es sehr ärgerlich ist, jedes Mal, wenn man einen Generator benötigt, ein neues Objekt zu erstellen - umso mehr in Python als C wegen der erforderlichen Ungeheuerlichkeit, die mit der Definition eines neuen Typs einhergeht. Und es kann keine yield Aussage in C geben, weil C keine Verschlüsse hat. Was ich stattdessen getan habe (da ich es nicht in der Python-API finden konnte - bitte zeigen Sie mich auf ein Standardobjekt, wenn es bereits existiert!) Erstellen Sie eine einfache, generische Generator Objektklasse, die eine C-Funktion für jede next() aufgerufen Methodenaufruf. Hier ist es (beachten Sie, dass ich noch nicht versucht, diese Zusammenstellung, weil es nicht abgeschlossen ist - siehe unten):

#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++); 
    } 
} 

Doch leider, ich bin noch nicht fertig. Die einzige Frage, die ich noch habe, ist: Wie erhebe ich eine StopIteration Ausnahme mit der C-API? Ich kann nicht scheinen, es in der Standard Exceptions aufgeführt zu finden. Vielleicht noch wichtiger, ist dies der richtige Weg, dieses Problem anzugehen?

Danke an alle, die das immer noch verfolgen.

+1

Sie sind sich bewusst, dass dies 'xrange (max)'? –

+1

Ja, aber das ist nur ein einfaches Beispiel. Ich habe einen praktischen Nutzen dafür. – Michael

Antwort

58

Im Folgenden ist eine einfache Implementierung des Moduls spam mit einer Funktion myiter(int) Rückkehr Iterator:

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

drucken Zahlen von 0 bis 9.

Es ist einfacher, dann Fall aber zeigt die Hauptpunkte: Definieren Objekt mit Standard __iter__() und next() Methoden und Iterator Verhalten der Umsetzung einschließlich StopIteration erhöhen, wenn angemessen.

In Ihrem Fall muss das Iterator-Objekt einen Verweis auf Sequence enthalten (Sie benötigen also eine Deallocator-Methode für Py_DECREF). Die Sequenz selbst muss implementieren und einen Iterator darin erstellen.


Struktur, die den Status des Iterators enthält. (In Ihrer Version anstelle von m, wäre es Bezug auf Sequenz haben.)

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

der Iterator __iter__() Methode. Es gibt immer einfach self zurück. Es ermöglicht sowohl Iterator und Sammlung gleich in Konstrukte wie for ... in ... behandelt werden.

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

Umsetzung unserer Iteration: next() Methode.

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; 
    } 
} 

Wir brauchen erweiterte Version von PyTypeObject Struktur Python mit Informationen über __iter__() und next() zur Verfügung zu stellen. Wir wollen, dass sie effizient aufgerufen werden, also keine Namen-basierte Suche im Wörterbuch.

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) Funktion erstellt Iterator.

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; 
} 

Der Rest ist ziemlich langweilig ...

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); 
} 
5

In Sequence_data müssen Sie entweder eine neue PyInt-Instanz zurückgeben oder eine StopIteration Exception werfen, die den Code außerhalb des Bereichs meldet, dass keine weiteren Werte vorhanden sind. Details siehe PEP 255 und 9.10 Generators.

Siehe Iterator Protocol für Hilfsfunktionen in der Python/C-API.

+0

Hier wird beschrieben, wie eine next() -Methode erstellt wird, jedoch nicht, wie ein Generatorobjekt erstellt und zurückgegeben wird, das die Methode enthält. – Michael

+0

Es ist sehr schwer, "Ertrag" in C zu simulieren; Machen Sie die 'Sequence'-Klasse stattdessen zu einem Iterator. –