2009-11-29 15 views
38

Làm cách nào để sao chép mã Python sau bằng API C C?Cách tạo trình tạo/trình vòng lặp với API C C?

class Sequence(): 
    def __init__(self, max): 
     self.max = max 
    def data(self): 
     i = 0 
     while i < self.max: 
      yield i 
      i += 1 

Cho đến nay, tôi có điều này:

#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? */ 
} 

Nhưng tôi không chắc chắn nơi để đi tiếp theo. Bất cứ ai có thể cung cấp một số gợi ý?

Sửa

Tôi cho rằng vấn đề chính tôi đang gặp với điều này được mô phỏng báo cáo kết quả yield. Theo tôi hiểu nó, nó là một cái nhìn khá đơn giản, nhưng trong thực tế phức tạp, tuyên bố - nó tạo ra một máy phát điện với riêng của mình __iter__()next() phương pháp được gọi tự động. Tìm kiếm thông qua các tài liệu, dường như nó được liên kết với PyGenObject; tuy nhiên, làm thế nào để tạo một thể hiện mới của đối tượng này là không rõ ràng. PyGen_New() lấy làm đối số của nó là PyFrameObject, tham chiếu duy nhất mà tôi có thể tìm thấy là PyEval_GetFrame(), dường như không phải là điều tôi muốn (hoặc tôi nhầm lẫn?). Có ai có kinh nghiệm với điều này họ có thể chia sẻ không?

Tiếp tục Sửa

Tôi thấy điều này là rõ ràng hơn khi tôi (chủ yếu) mở rộng những gì Python đang làm đằng sau hậu trường:

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) 

Về mặt kỹ thuật trình tự tắt bởi một nhưng bạn sẽ có được ý tưởng.

Vấn đề duy nhất với điều này là rất khó chịu khi tạo một đối tượng mới mỗi lần cần một trình tạo - thậm chí nhiều hơn trong Python so với C vì tính chất đơn yêu cầu đi kèm với việc xác định kiểu mới. Và có thể không có tuyên bố yield trong C vì C không có bao đóng. Những gì tôi đã làm thay vì (vì tôi không thể tìm thấy nó trong Python API - xin vui lòng chỉ cho tôi một đối tượng tiêu chuẩn nếu nó đã tồn tại!) Đã tạo một lớp đối tượng máy phát điện đơn giản, chung chung gọi lại một hàm C cho mỗi next() gọi phương thức. Đây là (lưu ý rằng tôi chưa thử biên dịch điều này vì nó chưa hoàn thành - xem bên dưới):

#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++); 
    } 
} 

Tuy nhiên, thật không may, tôi vẫn chưa kết thúc. Câu hỏi duy nhất tôi để lại là: Làm cách nào để tăng ngoại lệ StopIteration với API C? Tôi không thể tìm thấy nó được liệt kê trong số Standard Exceptions. Ngoài ra, có lẽ quan trọng hơn, đây có phải là cách chính xác để tiếp cận vấn đề này không?

Nhờ bất kỳ ai vẫn đang theo dõi điều này.

+1

Bạn đang nhận thức được rằng đây là 'xrange (max) '? –

+1

Có, nhưng đây chỉ là một ví dụ đơn giản. Tôi có một cách sử dụng thực tế cho việc này. – Michael

Trả lời

58

Dưới đây là một việc thực hiện đơn giản của mô-đun spam với một chức năng myiter(int) trở về iterator:

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

in số 0-9.Nó là đơn giản hơn sau đó trường hợp của bạn nhưng cho thấy điểm chính: xác định đối tượng với các tiêu chuẩn __iter__()next() phương pháp, và thực hiện hành vi lặp bao gồm nâng cao StopIteration khi thích hợp.

Trong đối tượng iterator trường hợp của bạn cần giữ tham chiếu đến Trình tự (vì vậy bạn sẽ cần phương thức deallocator cho nó để Py_DECREF nó). Trình tự cần thực hiện __iter()__ và tạo một trình lặp bên trong nó.


Cấu trúc chứa trạng thái của trình lặp. (Trong phiên bản của bạn thay vì m, nó sẽ có tham chiếu đến Sequence.)

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

phương pháp Iterator của __iter__(). Nó luôn luôn đơn giản trả về self. Nó cho phép cả bộ lặp và bộ sưu tập được xử lý giống nhau trong các cấu trúc như for ... in ....

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

Thực hiện phương thức lặp lại: 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; 
    } 
} 

Chúng ta cần phiên bản mở rộng của PyTypeObject cấu trúc để cung cấp Python với thông tin về __iter__()next(). Chúng tôi muốn chúng được gọi hiệu quả, vì vậy không tra cứu tên trong từ điển.

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) chức năng tạo trình lặp.

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

Phần còn lại là khá nhàm chán ...

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

Trong Sequence_data, bạn phải trả lại phiên bản PyInt mới hoặc ném một ngoại lệ StopIteration cho mã bên ngoài không có giá trị nào khác. Xem PEP 255 để biết chi tiết và 9.10 Generators.

Xem Iterator Protocol cho các chức năng trợ giúp trong API Python/C.

+0

Điều này mô tả cách tạo phương thức next(), nhưng không phải cách tạo và trả về đối tượng trình tạo chứa phương thức. – Michael

+0

Rất khó để mô phỏng "năng suất" trong C; làm cho lớp 'Sequence' trở thành một trình vòng lặp thay thế. –