Как создать генератор / итератор с помощью API Python C?

Как копировать следующий код Python с помощью 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 

Пока у меня есть это:

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

Но я не уверен, куда идти дальше. Может ли кто-нибудь предложить какие-то предложения?

редактировать

Я полагаю, что основная проблема, с которой я сталкиваюсь, заключается в моделировании утверждения yield . Насколько я понимаю, это довольно простой, но на самом деле сложный оператор – он создает генератор с его собственными __iter__() и next() которые вызываются автоматически. Поиск через документы, похоже, связан с PyGenObject ; однако, как создать новый экземпляр этого объекта, неясно. PyGen_New() принимает в качестве аргумента PyFrameObject , единственной ссылкой на которую я могу найти, является 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) 

Технически последовательность отключена на один, но вы получаете идею.

Единственная проблема с этим – очень раздражает создание нового объекта каждый раз, когда вам нужен генератор – тем более, в Python, чем C, из-за требуемого чудовища, которое связано с определением нового типа. И не может быть никакой инструкции yield в C, потому что C не имеет замыканий. То, что я сделал вместо этого (так как я не мог найти его в API Python – пожалуйста, укажите мне стандартный объект, если он уже существует!) Создавал простой, общий класс объектов-генераторов, который вызывал функцию C для каждого 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++); } } 

Однако, к сожалению, я все еще не закончен. Единственный вопрос, который у меня остался, – это: Как я могу StopIteration исключение StopIteration с C API? Кажется, я не могу найти его в Стандартных Исключениях . Кроме того, возможно, что более важно, правильно ли это подходит для решения этой проблемы?

Спасибо всем, кто все еще следит за этим.

2 Solutions collect form web for “Как создать генератор / итератор с помощью API Python C?”

Ниже приведена простая реализация spam модуля с одним возвращаемым итератором myiter(int) :

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

печатает цифры от 0 до 9.

Это проще, чем ваш случай, но показывает основные моменты: определение объекта со стандартными __iter__() и next() методами и реализация поведения итератора, включая повышение StopIteration когда это необходимо.

В вашем случае объект-итератор должен содержать ссылку на Sequence (так что для этого вам понадобится метод deallocator для Py_DECREF). Сама последовательность должна реализовать __iter()__ и создать внутри нее итератор.


Структура, содержащая состояние итератора. (В вашей версии вместо m она будет ссылаться на Sequence.)

 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) создает итератор.

 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, либо StopIteration исключение StopIteration которое сообщает внешнему коде, что больше нет значений. Подробнее см. PEP 255 и 9.10 Генераторы .

См. Протокол Iterator для вспомогательных функций в API Python / C.

  • Как поймать stdout python в c ++-коде
  • встраивание ошибки модуля python
  • Вызов функции Python из программы C
  • Передача функции C в функцию Python
  • Файловый ввод-вывод в API Python 3 C
  • Как передать флаг в gcc в скрипте setup.py Python?
  • PyArg_ParseTuple SegFaults в CApi
  • вложение ошибки python при инициализации
  • Python - лучший язык программирования в мире.