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

Я когда-то использовал шаблонное метапрограммирование в C++ для реализации техники, названной "символьное возмущение" для контакта с вырожденным входом в геометрических алгоритмах. Путем представления арифметических выражений как вложенных шаблонов (т.е. в основном путем выписывания деревьев синтаксического анализа вручную) я смог вручить от всего анализа выражения шаблонному процессору.

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

, Конечно, Вы могли выполнить то же самое путем реализации маленького DSL (предметно-ориентированный язык) для выражений и вставки переведенного кода C++ в обычную программу. Это получило бы Вас все равно преимущества оптимизации и также было бы более четким - но компромисс - то, что необходимо поддержать синтаксический анализатор.

43
задан Michael 29 November 2009 в 23:01
поделиться

2 ответа

Ниже представлена ​​простая реализация модуля spam с одной функцией myiter (int) , возвращающей итератор:

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

печатает числа от 0 до 9.

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

В вашем случае объект-итератор должен содержать ссылку на Sequence (поэтому вам понадобится метод освобождения для Py_DECREF it). Сама последовательность должна реализовать __ 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);
}
61
ответ дан 26 November 2019 в 22:59
поделиться

В Sequence_data вы должны либо вернуть новый экземпляр PyInt, либо вызвать исключение StopIteration , которое сообщает внешнему коду, что больше нет значений. См. PEP 255 для получения подробной информации и 9.10 Генераторы .

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

6
ответ дан 26 November 2019 в 22:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: