Appearance
生成器和迭代器是相互依赖的,迭代器每次需要一个值,调用生成器返回一直,直到生成器返回集合所有的值。这个过程是交替执行的。
迭代器
迭代器是一个可以记住遍历的位置的对象。从集合的第一个元素开始遍历,直至集合所有的元素都被访问完毕。
接下来会使用 C/C++ 实现迭代器的遍历,用 C/C++ 实现一个传参为迭代器对象的函数,内部会先调用 PyObject_GetIter
将对象转化为一个可迭代的对象,然后循环调用 PyIter_Next
,直至返回 NULL,在遍历的过程中调用 PyObject_Print
输出集合元素,并减少 item
的引用次数,最后通过 Py_BuildValue
构建一个空的返回值。
C/C++ 实现,实现迭代器
c
// main.cpp
#include "Python.h"
#include <stdio.h>
static PyObject *iter_print(PyObject *self, PyObject *args) {
PyObject *iter;
if (!PyArg_ParseTuple(args, "O", &iter)) {
return NULL;
}
PyObject *item = NULL;
PyObject *iterator = PyObject_GetIter(iter);
if (iterator == NULL) {
return NULL;
}
while ((item = PyIter_Next(iterator))) {
PyObject_Print(item, stdout, Py_PRINT_RAW);
printf("\n");
Py_DECREF(item);
}
Py_DECREF(iterator);
return Py_BuildValue("");
}
static PyObject *gen(PyObject *self, PyObject *args) {
PyObject* obj = NULL;
return obj;
}
static PyMethodDef MyDemoMethods[] = {
{"iter_print", iter_print, METH_VARARGS, "iter print"},
{"gen", gen, METH_VARARGS, "gen"},
{nullptr, nullptr, 0, nullptr},
};
static struct PyModuleDef spammodule = {
PyModuleDef_HEAD_INIT,
"demo", /* name of module */
NULL, /* module documentation, may be NULL */
-1, /* size of per-interpreter state of the module,
or -1 if the module keeps state in global variables. */
MyDemoMethods
};
PyMODINIT_FUNC PyInit_demo(void) {
return PyModule_Create(&spammodule);
}
编写 setup.py 安装动态链接库,分发模块
执行 python setup.py install
将动态链接库生出,并拷贝至系统模块中,方便程序导入调用
python
# setup.py
from distutils.core import setup, Extension
setup(name="demo", version="1.0",
ext_modules=[Extension("spam", ["iter.cpp"])])
编写 Python 程序,测试结果
如下程序执行 python test.py
可以看到输出结果为 0 ~ 19 的整数,证明我们的程序是正确的。
python
# test.py
import demo
def test():
for i in range(20):
yield i
demo.iter_print(test())
生成器
生成器是特殊的子程序,类似返回数组的函数,具备参数,可以被调用,产生一些列的值,但是所有的值不是一次性返回,每次产生一个值。
编写 C/C++ 代码,实现生成器
如下程序是实现一个反转列表的生成器,倒序输出列表中的值。生成器是一个类。
生成器相对于迭代器略微复杂,首先我们需要自定义一个类 RevgenState
,与之前的 C/C++ 实现扩展类原理相似,中间多了一个环节就是实现 next
的调用过程 revgen_next
。
首先程序需要判断游标是否已经遍历完所有的元素,如果遍历结束返回空 NULL。否则返回对应索引的值,并移动索引。PySequence_GetItem
第一个参数为列表,第二个参数为索引,帮助我们快速检索元素,程序拿到返回值返回给外部。
c
#include "Python.h"
typedef struct {
PyObject_HEAD
Py_ssize_t seq_index, enum_index;
PyObject *sequence;
} RevgenState;
static PyObject *revgen_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) {
PyObject *sequence;
if (!PyArg_UnpackTuple(args, "revgen", 1, 1, &sequence))
return NULL;
if (!PySequence_Check(sequence)) {
PyErr_SetString(PyExc_TypeError, "revgen() expects a sequence");
return NULL;
}
Py_ssize_t len = PySequence_Length(sequence);
if (len == -1)
return NULL;
RevgenState *rgstate = (RevgenState *) type->tp_alloc(type, 0);
if (!rgstate)
return NULL;
Py_INCREF(sequence);
rgstate->sequence = sequence;
rgstate->seq_index = len - 1;
rgstate->enum_index = 0;
return (PyObject *) rgstate;
}
static void revgen_dealloc(RevgenState *rgstate) {
Py_XDECREF(rgstate->sequence);
Py_TYPE(rgstate)->tp_free(rgstate);
}
static PyObject *revgen_next(RevgenState *rgstate) {
if (rgstate->seq_index >= 0) {
PyObject *elem = PySequence_GetItem(rgstate->sequence,
rgstate->seq_index);
if (elem) {
PyObject *result = Py_BuildValue("lO", rgstate->enum_index, elem);
rgstate->seq_index--;
rgstate->enum_index++;
return result;
}
}
rgstate->seq_index = -1;
Py_CLEAR(rgstate->sequence);
return NULL;
}
PyTypeObject PyRevgen_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"revgen", /* tp_name */
sizeof(RevgenState), /* tp_basicsize */
0, /* tp_itemsize */
(destructor) revgen_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
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, /* tp_flags */
0, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
PyObject_SelfIter, /* tp_iter */
(iternextfunc) revgen_next, /* 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 */
PyType_GenericAlloc, /* tp_alloc */
revgen_new, /* tp_new */
};
static struct PyModuleDef spammodule = {
PyModuleDef_HEAD_INIT,
"spam", /* m_name */
"", /* m_doc */
-1, /* m_size */
};
PyMODINIT_FUNC
PyInit_spam(void) {
PyObject *module = PyModule_Create(&spammodule);
if (!module)
return NULL;
if (PyType_Ready(&PyRevgen_Type) < 0)
return NULL;
Py_INCREF((PyObject *) &PyRevgen_Type);
PyModule_AddObject(module, "revgen", (PyObject *) &PyRevgen_Type);
return module;
}
编写测试程序,测试生成器和迭代器
执行如下程序python test.py
,输出结果依次为(0, 'c')
, (1, 'b')
, (2, 'a')
。从结果来看已经达到了此次实验的目的。
python
# test.py
import demo
import spam
demo.iter_print(spam.revgen(['a', 'b', 'c']))
总结
从以上实验的过程和结果来看,C/C++ 实现生成器和迭代器并没有想象的那么复杂,套用模板即可。了解 C/C++ 开发 Python 扩展的整个流程之后,照搬模板就显得游刃有余了,简单点的参数调试和测试也十分的简单。
迭代器和生成器的出现可以避免一次性将数据加载到内存中,减少内存分配,腾出更多的内存空间给其他的模块。