Python C API를 사용하여 생성기/반복기를 만드는 방법은 무엇입니까?
다음 Python 코드를 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
지금까지, 저는 다음과 같습니다.
#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
내가 찾을 수 있는 유일한 참고 자료는 내가 원하는 것이 아닌 것 같다(혹은 내가 틀렸나?).이 일에 대해 공유할 수 있는 경험이 있는 사람이 있습니까?
추가 편집
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)
엄밀히 말하면 시퀀스는 1개씩 어긋나지만, 이해하실 수 있습니다.
여기서 유일한 문제는 생성기가 필요할 때마다 새로운 개체를 만드는 것이 매우 귀찮다는 것입니다. 새로운 유형을 정의할 때 필요한 괴물이 있기 때문에 Python에서는 C보다 더 그렇습니다.그리고 있을 수 없다.yield
C에는 폐쇄가 없기 때문에 C에 있는 스테이트먼트.대신 제가 한 일은 (Python API에서 찾을 수 없었기 때문에 -- 표준 객체가 이미 존재한다면 저를 가리켜주세요!) 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
C API에 대한 예외는 무엇입니까?표준 예외에 나열되어 있지 않은 것 같습니다.또한, 아마도 더 중요한 것은 이것이 이 문제에 접근하는 올바른 방법인가?
아직도 이걸 따라다니는 사람들 덕분이야
다음은 모듈의 간단한 구현입니다.spam
하나의 기능으로myiter(int)
반복기 반환:
import spam
for i in spam.myiter(10):
print i
0 ~ 9 의 숫자를 인쇄합니다.
은 당신의 : 입니다.표준으로 오브젝트 정의__iter__()
★★★★★★★★★★★★★★★★★」next()
이 동작에는, 「반복자」의 「반복습자 동작에는, 「반복자」가 포함됩니다.StopIteration
적절한 경우.
이 경우 반복기 개체는 Sequence에 대한 참조를 유지해야 합니다(따라서 Py_DECREF에 할당 해제 메서드가 필요합니다).는 실장할 가 있습니다.__iter()__
그 안에 반복기를 만듭니다.
반복기 상태를 포함하는 구조입니다.(m이 아닌 버전에서는 Sequence를 참조합니다.)
typedef struct {
PyObject_HEAD
long int m;
long int i;
} spam_MyIter;
의 ★★★★__iter__()
it. 항상 간단하게 always it returns returns returns returns returns returns returns returns returns returns returns it returns it returns 。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
외부 코드에 값이 더 이상 없음을 알려주는 예외입니다.자세한 내용은 PEP 255 및 9.10 제너레이터를 참조하십시오.
Python/C API의 도우미 함수는 Iterator Protocol을 참조하십시오.
언급URL : https://stackoverflow.com/questions/1815812/how-to-create-a-generator-iterator-with-the-python-c-api
'programing' 카테고리의 다른 글
node.js를 사용하여 원하는 ID를 생성하는 방법 (0) | 2022.09.21 |
---|---|
값을 반환하지 않고 비포이드 함수의 끝에서 흘러내리면 컴파일러 오류가 발생하지 않는 이유는 무엇입니까? (0) | 2022.08.17 |
에러노 스레드는 안전합니까? (0) | 2022.08.17 |
코드의 그림 함수 호출 그래프를 가져오는 도구 (0) | 2022.08.17 |
Cython 대신 Python/C 인터페이스를 사용하는 장점이 있습니까? (0) | 2022.08.17 |