2009-11-29 83 views
38

如何使用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为参数,我可以找到的唯一参考是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更加麻烦,因为定义一个新类型需要的怪物。 C中没有yield语句,因为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++); 
    } 
} 

然而,不幸的是,我还没有完成。我留下的唯一问题是:如何通过C API提出StopIteration异常?我似乎无法找到它在Standard Exceptions中列出。另外,也许更重要的是,这是解决这个问题的正确方法吗?

感谢任何人仍然关注此事。

+1

你都知道,这是'x范围(最大)'? – 2009-11-29 15:33:09

+1

是的,但这只是一个简单的例子。我对此有实际用处。 – Michael 2009-11-29 19:36:02

回答

58

下面是一个简单的实现模块spam与一种功能myiter(int)返回迭代:

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

打印编号从0到9。

然后您的案例更简单,但显示了要点:使用标准__iter__()next()方法定义对象,并在适当的时候实现迭代器行为,包括提升StopIteration

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


包含迭代器状态的结构。 (在你的版本,而不是男,那就得参考序列。)

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

Sequence_data中,您必须返回一个新的PyInt实例或抛出一个StopIteration异常,它告诉外部代码没有更多的值。详情请参阅PEP 2559.10 Generators。对于Python/C API中的助手函数,请参阅Iterator Protocol

+0

这个描述了如何创建next()方法,但不是如何创建并返回包含该方法的生成器对象。 – Michael 2009-11-29 18:54:38

+0

在C中模拟“yield”非常困难;取而代之的是使'Sequence'类成为一个迭代器。 – 2009-11-30 08:27:39