2009-11-29 23 views
38

次のPythonコードをPython C APIでどのように複製するのですか?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()です。これは私が欲しいものではないようです(または間違っていますか?)。誰もが共有できるこれに関する経験はありますか?

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は舞台裏で何をやっていた拡張したとき、これは明確であることが判明しました。

唯一の問題は、ジェネレータが必要なたびに新しいオブジェクトを作成することです(新しいタイプを定義する際に必要となる怪獣のために、PythonではCよりもさらに新しいオブジェクトを作成することは非常に面倒です)。また、Cにはクロージャがないため、yieldステートメントがCに存在しない可能性があります。代わりに何をしましたか(私がPython APIで見つけることができなかったからです。は既に存在すれば標準オブジェクトを指すようにしてください)は、すべてnext()のC関数をコールバックした単純なジェネリックジェネレータオブジェクトクラスを作成しましたメソッド呼び出し。ここでは、(それが完了していないので、私はまだこれをコンパイルしようとしていないことに注意してください - 下記参照)されています。しかし、残念ながら、私はまだ終わっていないよ

#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

かなり退屈なのですか? –

+1

はい、これは単なる例に過ぎません。私はこれを実際に使用しています。 – Michael

答えて

58

以下は一つの機能myiter(int)返すイテレータとモジュールspamの単純な実装です。

標準の__iter__()next()メソッドを持つオブジェクトを定義し、適切な場合にはStopIterationを含むイテレータの動作を実装するという主要な点が示されています。

あなたのケースでは、反復子オブジェクトはSequenceへの参照を保持する必要があります(したがって、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; 
    } 
} 

我々は__iter__()next()について 情報ではPythonを提供するために、PyTypeObject構造の拡張版が必要です。 それらを効率的に呼び出すようにしたいので、辞書に名前ベースのルックアップはありません。

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

残りはあなたが、これは `はxrange(最大)`であることを知っている...

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から9まで

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

印刷番号:

+0

これはnext()メソッドを作成する方法を記述しますが、そのメソッドを含むジェネレータオブジェクトを作成して返す方法は示しません。 – Michael

+0

Cで "yield"をシミュレートするのは非常に難しいです。代わりに 'Sequence'クラスをイテレータにしてください。 –

関連する問題