如何用Python C API创build一个生成器/迭代器?

如何使用Python C API复制以下Python代码?

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

到目前为止,我有这样的:

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

但我不确定下一步该去哪里。 谁能提供一些build议?

编辑

我想这个主要的问题是模拟yield语句。 据我了解,这是一个相当简单的,但实际上是复杂的,声明 – 它创build一个自动调用__iter__()next()方法的生成器。 通过search文档,它似乎与PyGenObject相关联; 但是,如何创build这个对象的新实例还不清楚。 PyGen_New()PyGen_New()作为参数,我可以find的唯一参考是PyEval_GetFrame() ,这似乎不是我想要的(或者我错了吗?)。 有没有人有这方面的经验,他们可以分享?

进一步编辑

当我(实质上)扩展了Python在幕后的工作时,我发现这更清晰了:

 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) 

从技术上讲,顺序是closures的,但你明白了。

唯一的问题是,每次需要一个生成器时创build一个新对象是非常烦人的 – 在Python中比C更多,因为定义一个新types需要的怪异性。 因为C没有closures,所以在C中没有yield语句。 我做了什么(因为我不能在Python API中find它 – 指向一个标准对象,如果它已经存在!)创build一个简单的,通用的生成器对象类,每next()方法调用。 这是(注意,我还没有试过编译这个,因为它不完整 – 见下文):

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

但不幸的是,我还没有完成。 我留下的唯一问题是:如何用C API引发StopIterationexception? 我似乎无法find它列在标准例外 。 另外,也许更重要的是,这是解决这个问题的正确方法吗?

感谢任何依然如此的人。

下面是一个函数myiter(int)返回迭代器模块spam的简单实现:

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

打印数字从0到9。

那么你的情况就简单了,但是显示了一些要点:用标准的__iter__()next()方法定义对象,并且实现迭代器行为,包括在适当的时候引发StopIteration

在你的情况下,迭代器对象需要持有对序列的引用(所以你需要deallocator方法将其引用到Py_DECREF中)。 序列本身需要实现__iter()__并在其中创build一个迭代器。


包含迭代器状态的结构。 (在你的版本中,而不是m,它会引用序列。)

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

迭代器的__iter__()方法。 它总是简单地回报self 。 它允许迭代器和集合在像for ... in ...一样的结构中被处理。

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

我们迭代的实现: 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; } } 

我们需要扩展版本的PyTypeObject结构来为Python提供关于__iter__()next() 。 我们希望它们被有效地调用,所以在字典中没有基于名称的查找。

 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)函数创build迭代器。

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

其余的很无聊…

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

Sequence_data ,你必须返回一个新的PyInt实例,或者抛出一个StopIterationexception,告诉外面的代码没有更多的值。 详情请参阅PEP 255和9.10发电机 。

请参阅Python / C API中的辅助函数的迭代器协议 。