How to create a generator/iterator with the Python C API?

后端 未结 2 1550
感动是毒
感动是毒 2020-12-12 13:45

How do I replicate the following Python code with the Python C API?

class Sequence():
    def __init__(self, max):
        self.max = max
    def data(self):
         


        
相关标签:
2条回答
  • 2020-12-12 14:17

    In Sequence_data, you must either return a new PyInt instance or throw a StopIteration exception which tells the code outside that there are no more values. See PEP 255 for details and 9.10 Generators.

    See Iterator Protocol for helper functions in the Python/C API.

    0 讨论(0)
  • 2020-12-12 14:39

    Below is a simple implementation of module spam with one function myiter(int) returning iterator:

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

    prints numbers from 0 to 9.

    It is simpler then your case but shows main points: defining object with standard __iter__() and next() methods, and implementing iterator behaviour including raising StopIteration when appropriate.

    In your case iterator object needs to hold reference to Sequence (so you'll need deallocator method for it to Py_DECREF it). The sequence itself needs to implement __iter()__ and create an iterator inside it.


    Structure containing state of iterator. (In your version instead of m, it would have reference to Sequence.)

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

    Iterator's __iter__() method. It always simply returns self. It allows for both iterator and collection to be treated the same in constructs like for ... in ....

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

    Implementation of our iteration: next() method.

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

    We need extended version of PyTypeObject structure to provide Python with information about __iter__() and next(). We want them to be called efficiently, so no name-based lookup in dictionary.

    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) function creates iterator.

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

    The rest is pretty boring...

    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);
    }
    
    0 讨论(0)
提交回复
热议问题