2012-01-08 43 views
6

Tôi đã viết một phần mở rộng Python cho một thư viện C. Tôi có cấu trúc dữ liệu trông như sau:SWIG interfacing thư viện C vào Python (Tạo 'lặp lại' kiểu dữ liệu Python từ cấu trúc C 'sequence')

typedef struct _mystruct{ 
    double * clientdata; 
    size_t len; 
} MyStruct; 

Mục đích của kiểu dữ liệu này ánh xạ trực tiếp vào kiểu dữ liệu danh sách bằng Python. Do đó, tôi muốn tạo hành vi 'giống như danh sách' cho cấu trúc đã xuất, vì vậy mã được viết bằng cách sử dụng phần mở rộng C của tôi là 'Pythonic' hơn.

Cụ thể, đây là những gì tôi muốn có thể làm (từ mã python) Lưu ý: py_ctsruct là kiểu dữ liệu ctsruct được truy cập trong python.

yêu cầu của tôi có thể được sumarized như:

  1. danh sách (py_ctsruct) trả về một danh sách python với tất cả các nội dung sao chép ra khỏi struct c
  2. py_cstruct [i] trả thứ i yếu tố (tốt nhất ném IndexError trên chỉ mục không hợp lệ)
  3. cho elem trong py_ctsruct: khả năng liệt kê

Theo PEP234, Một đối tượng có thể được lặp lại với "cho" nếu nó thực hiện _ iter _() hoặc _ GetItem _(). Sử dụng logic đó, tôi nghĩ rằng bằng cách thêm các thuộc tính sau (thông qua rename) vào tệp giao diện SWIG của tôi, tôi sẽ có hành vi mong muốn (ngoài req. # 1 ở trên - mà tôi vẫn không biết cách đạt được):

__len__ 
__getitem__ 
__setitem__ 

Tôi hiện có thể lập chỉ mục đối tượng C trong python. Tôi chưa thực hiện việc ném ngoại lệ Python, tuy nhiên nếu vượt quá giới hạn mảng, sẽ trả về một số ma thuật (mã lỗi).

Điều thú vị là khi tôi cố gắng để lặp qua các struct sử dụng 'cho x trong' cú pháp ví dụ:

for i in py_cstruct: 
    print i 

Python đi vào một vòng lặp vô hạn mà chỉ đơn giản in sự kỳ diệu (lỗi) số đề cập ở trên, trên bảng điều khiển. điều này gợi ý với tôi rằng có điều gì đó sai trái với việc lập chỉ mục.

cuối cùng nhưng không kém phần quan trọng nhất, làm cách nào để triển khai yêu cầu 1? điều này liên quan (như tôi hiểu nó):

  • xử lý' các chức năng cuộc gọi danh sách () từ python
  • Trả về một Python (danh sách) kiểu dữ liệu từ mã C

[[ Cập nhật]]

Tôi muốn xem một đoạn mã nhỏ về những khai báo (nếu có) tôi cần đặt trong tệp giao diện của mình, để tôi có thể lặp qua các phần tử của c ruct, từ Python.

Trả lời

17

Giải pháp đơn giản nhất để điều này là triển khai __getitem__ và ném một ngoại lệ IndexError cho chỉ mục không hợp lệ.

Tôi đặt cùng một ví dụ của việc này, sử dụng %extend%exception trong SWIG để thực hiện __getitem__ và nâng cao một ngoại lệ tương ứng:

%module test 

%include "exception.i" 

%{ 
#include <assert.h> 
#include "test.h" 
static int myErr = 0; // flag to save error state 
%} 

%exception MyStruct::__getitem__ { 
    assert(!myErr); 
    $action 
    if (myErr) { 
    myErr = 0; // clear flag for next time 
    // You could also check the value in $result, but it's a PyObject here 
    SWIG_exception(SWIG_IndexError, "Index out of bounds"); 
    } 
} 

%include "test.h" 

%extend MyStruct { 
    double __getitem__(size_t i) { 
    if (i >= $self->len) { 
     myErr = 1; 
     return 0; 
    } 
    return $self->clientdata[i]; 
    } 
} 

Tôi đã thử nghiệm nó bằng cách thêm vào test.h:

static MyStruct *test() { 
    static MyStruct inst = {0,0}; 
    if (!inst.clientdata) { 
    inst.len = 10; 
    inst.clientdata = malloc(sizeof(double)*inst.len); 
    for (size_t i = 0; i < inst.len; ++i) { 
     inst.clientdata[i] = i; 
    } 
    } 
    return &inst; 
} 

Và chạy Python sau:

import test 

for i in test.test(): 
    print i 

Bản in nào:

python run.py 
0.0 
1.0 
2.0 
3.0 
4.0 
5.0 
6.0 
7.0 
8.0 
9.0 

và sau đó kết thúc.


Một cách tiếp cận khác, sử dụng một typemap để lập bản đồ MyStruct lên một PyList trực tiếp có thể quá:

%module test 

%{ 
#include "test.h" 
%} 

%typemap(out) (MyStruct *) { 
    PyObject *list = PyList_New($1->len); 
    for (size_t i = 0; i < $1->len; ++i) { 
    PyList_SetItem(list, i, PyFloat_FromDouble($1->clientdata[i])); 
    } 

    $result = list; 
} 

%include "test.h" 

này sẽ tạo ra một PyList với giá trị trả về từ bất kỳ hàm trả về một MyStruct *. Tôi đã thử nghiệm này %typemap(out) với chức năng chính xác giống như phương pháp trước đó.

Bạn cũng có thể viết một ứng %typemap(in)%typemap(freearg) cho điều ngược lại, một cái gì đó như mã chưa được kiểm tra này:

%typemap(in) (MyStruct *) { 
    if (!PyList_Check($input)) { 
    SWIG_exception(SWIG_TypeError, "Expecting a PyList"); 
    return NULL; 
    } 
    MyStruct *tmp = malloc(sizeof(MyStruct)); 
    tmp->len = PyList_Size($input); 
    tmp->clientdata = malloc(sizeof(double) * tmp->len); 
    for (size_t i = 0; i < tmp->len; ++i) { 
    tmp->clientdata[i] = PyFloat_AsDouble(PyList_GetItem($input, i)); 
    if (PyErr_Occured()) { 
     free(tmp->clientdata); 
     free(tmp); 
     SWIG_exception(SWIG_TypeError, "Expecting a double"); 
     return NULL; 
    } 
    } 
    $1 = tmp; 
} 

%typemap(freearg) (MyStruct *) { 
    free($1->clientdata); 
    free($1); 
} 

Sử dụng một iterator sẽ có ý nghĩa hơn đối với container như danh sách liên kết, nhưng cho đầy đủ vì lợi ích đây là cách bạn có thể thực hiện nó cho MyStruct với __iter__. Điều quan trọng là bạn lấy SWIG để bọc một loại khác cho bạn, cung cấp __iter__()next() cần thiết, trong trường hợp này là MyStructIter được xác định và gói cùng một lúc bằng cách sử dụng %inline vì nó không phải là một phần của API C bình thường:

%module test 

%include "exception.i" 

%{ 
#include <assert.h> 
#include "test.h" 
static int myErr = 0; 
%} 

%exception MyStructIter::next { 
    assert(!myErr); 
    $action 
    if (myErr) { 
    myErr = 0; // clear flag for next time 
    PyErr_SetString(PyExc_StopIteration, "End of iterator"); 
    return NULL; 
    } 
} 

%inline %{ 
    struct MyStructIter { 
    double *ptr; 
    size_t len; 
    }; 
%} 

%include "test.h" 

%extend MyStructIter { 
    struct MyStructIter *__iter__() { 
    return $self; 
    } 

    double next() { 
    if ($self->len--) { 
     return *$self->ptr++; 
    } 
    myErr = 1; 
    return 0; 
    } 
} 

%extend MyStruct { 
    struct MyStructIter __iter__() { 
    struct MyStructIter ret = { $self->clientdata, $self->len }; 
    return ret; 
    } 
} 

các yêu cầu cho iteration over containers là như vậy mà container cần phải thực hiện __iter__() và trả về một iterator mới, nhưng ngoài next() mà trả về mục tiếp theo và gia số iterator iterator bản thân cũng phải cung cấp một phương pháp __iter__(). Điều này có nghĩa là một trong hai container hoặc một iterator có thể được sử dụng giống hệt nhau.

MyStructIter cần theo dõi trạng thái lặp hiện tại - vị trí của chúng tôi và số lượng chúng ta còn lại. Trong ví dụ này, tôi đã làm điều đó bằng cách giữ một con trỏ đến mục tiếp theo và một bộ đếm mà chúng ta sử dụng để nói khi nào chúng ta nhấn vào cuối. Bạn cũng có thể lưu giữ theo dõi của sate bằng cách giữ một con trỏ đến MyStruct iterator đang sử dụng và truy cập một cho vị trí trong đó, một cái gì đó như:

%inline %{ 
    struct MyStructIter { 
    MyStruct *list; 
    size_t pos; 
    }; 
%} 

%include "test.h" 

%extend MyStructIter { 
    struct MyStructIter *__iter__() { 
    return $self; 
    } 

    double next() { 
    if ($self->pos < $self->list->len) { 
     return $self->list->clientdata[$self->pos++]; 
    } 
    myErr = 1; 
    return 0; 
    } 
} 

%extend MyStruct { 
    struct MyStructIter __iter__() { 
    struct MyStructIter ret = { $self, 0 }; 
    return ret; 
    } 
} 

(Trong trường hợp này chúng ta có thể thực sự vừa sử dụng tự đóng gói làm trình vòng lặp làm trình lặp, bằng cách cung cấp một số __iter__() trả lại một bản sao của vùng chứa và next() tương tự như loại đầu tiên.Tôi đã không làm điều đó trong câu trả lời ban đầu của tôi bởi vì tôi nghĩ rằng sẽ ít rõ ràng hơn có hai loại riêng biệt - một vùng chứa và một trình lặp cho vùng chứa đó)

+0

Cảm ơn các đoạn trích. Câu hỏi nhanh, bạn đã thử nghiệm điều này từ đầu Python chưa ?. Tôi không biết nhiều về typemaps và PyList/PyTuple nhưng tôi nghi ngờ rằng chúng gần gũi hơn với những gì tôi muốn đạt được (tức là xử lý cấu trúc C như một kiểu chuỗi Python). Tuy nhiên, điểm chính là vấn đề chính của tôi là tôi không thể lặp qua các phần tử trong cấu trúc C. có lẽ phơi bày cấu trúc C dưới dạng danh sách hoặc bộ túp giúp giết hai con chim (kiểm tra vòng lặp và mảng). Đó là hai vấn đề tôi đang cố gắng giải quyết - là typemap + PyList con đường phía trước ?. Bạn nghĩ sao? –

+0

@HomunculusReticulli - Tôi đã thử nghiệm tất cả mọi thứ từ phía Python, sử dụng hàm 'test()' mà tôi đã chỉ ra, mặc dù đối với các typemaps, tôi chỉ thử nghiệm '% typemap (out)', nhưng cả hai phương thức đều làm việc tốt với 'for i trong ... 'Xây dựng Python. – Flexo

+0

@HomunculusReticulli - Tôi cũng đã thêm biến '__iter __()' vào câu trả lời của mình mặc dù tôi thích phiên bản '__getitem __()' hơn trong trường hợp này. – Flexo

1
  1. Tra cứu bằng cách sử dụng lệnh% typemap swig. http://www.swig.org/Doc2.0/SWIGDocumentation.html#Typemaps http://www.swig.org/Doc2.0/SWIGDocumentation.html#Typemaps_nn25 Typography thành viên có thể làm những gì bạn muốn. http://www.swig.org/Doc2.0/SWIGDocumentation.html#Typemaps_nn35 Tôi có một typemap mà tôi tìm thấy trong phần Python cho phép tôi chuyển dữ liệu char ** vào C++ dưới dạng danh sách các chuỗi Python. Tôi đoán sẽ có chức năng tương tự.
  2. Ngoài ra, bạn có thể xác định% pythoncode trong giao diện bên trong cấu trúc bên trong tệp "i". Điều này sẽ cho phép bạn thêm các phương thức python vào đối tượng được tạo cho cấu trúc. Có một lệnh% addmethod (tôi nghĩ) cho phép bạn thêm các phương thức vào cấu trúc hoặc một lớp. Sau đó, bạn có thể tạo các phương thức để lập chỉ mục các đối tượng trong C++ hoặc C nếu bạn muốn. Có rất nhiều cách để giải quyết vấn đề này.

Đối với giao diện Tôi đang làm việc trên Tôi đã sử dụng đối tượng lớp có một số phương pháp để truy cập dữ liệu trong mã của tôi. Những phương thức này được viết bằng C++. Sau đó, tôi đã sử dụng chỉ thị% pythoncode bên trong lớp bên trong tệp "i" và đã tạo các phương thức "C" trong Python bằng cách sử dụng phương thức C++ để làm cho nó trông giống như truy cập kiểu từ điển.

+0

+1 cho đề xuất% pythoncode. Nó cho phép tôi thực hiện một số chức năng khác (truy cập các thuộc tính đối tượng). Tôi vẫn không thể lặp (hợp lý) trên các cấu trúc C của tôi trong Python mặc dù. –

+0

Tôi nghĩ rằng bạn cần phải gõ sơ đồ dữ liệu trong cấu trúc. Tôi đã bao gồm một số liên kết. Chức năng memberin có thể làm những gì bạn đang suy nghĩ. Bạn có thể cần phải viết một số hàm mã C điều khiển mảng đối tượng và sử dụng các hàm đó bên trong các phương thức% pythoncode. Hãy cho tôi biết nếu bạn cần thêm ý tưởng. – Demolishun

0

Bạn nói rằng bạn chưa triển khai thực hiện việc ném ngoại lệ Python - đó là vấn đề. Từ PEP 234:

Một ngoại lệ mới được xác định, StopIteration, có thể được sử dụng để báo hiệu kết thúc của một lần lặp.

Bạn phải đặt ngoại lệ này vào cuối lần lặp lại của mình. Kể từ mã của bạn không làm điều này, bạn đang chạy vào tình huống mà bạn đã mô tả:

  1. Các thông dịch vòng qua tùy chỉnh iternext chức năng của danh sách của bạn
  2. Chức năng của bạn được đến cuối của mảng, và thay vì thiết lập chính xác ngoại lệ StopIteration, chỉ cần trả về 'số ma thuật' của bạn.
  3. Thông dịch viên, không thấy lý do chính đáng để ngừng lặp lại, chỉ cần tiếp tục in giá trị được trả về bởi iternext ... số ma thuật của bạn. Đối với thông dịch viên, đó chỉ là một thành viên khác trong danh sách.

May mắn thay, đây là một sửa chữa khá đơn giản, mặc dù có thể không có vẻ đơn giản, vì C không có cơ sở ngoại lệ. Python C API chỉ đơn giản sử dụng một chỉ báo lỗi toàn cục mà bạn thiết lập khi một tình huống ngoại lệ được nâng lên, và sau đó các tiêu chuẩn API ra lệnh cho bạn trả về NULL tất cả cách chồng lên trình thông dịch, sau đó xem kết quả của PyErr_Occurred() để xem một lỗi được đặt và nếu có, hãy in ngoại lệ và traceback có liên quan.

Vì vậy, trong chức năng của bạn, khi bạn đến cuối của mảng, bạn chỉ cần điều này:

PyErr_SetString(PyExc_StopIteration,"End of list"); 
return NULL; 

Đây là một câu trả lời tuyệt vời cho đọc thêm về vấn đề này: How to create a generator/iterator with the Python C API?

1

Tôi gặp phải vấn đề rất giống với Python 2.6 và giải quyết nó nhờ @aphex trả lời. Nhưng tôi muốn tránh bất kỳ giá trị ma thuật nào, hoặc thêm boolean để vượt qua tình trạng cuối danh sách. Chắc chắn rồi, trình lặp của tôi có một phương thức atEnd() cho tôi biết rằng tôi đã kết thúc danh sách.

Vì vậy, trên thực tế, việc xử lý ngoại lệ SWIG khá dễ dàng. Tôi chỉ cần thêm ma thuật sau đây:

%ignore MyStructIter::atEnd(); 
%except MyStructIter::next { 
    if($self->list->atEnd()) { 
     PyErr_SetString(PyExc_StopIteration,"End of list"); 
     SWIG_fail; 
    } 
    $action 
} 

Điểm chính là lần bỏ qua cuộc gọi này ngay sau khi bạn kết thúc danh sách.

Nếu bạn dính vào thành ngữ của bạn, nó sẽ giống như thế:

%except MyStructIter::next { 
    if($self->pos >= $self->list->len) { 
     PyErr_SetString(PyExc_StopIteration,"End of list"); 
     SWIG_fail; 
    } 
    $action 
} 

LƯU Ý CHO PYTHON 3.x:

Bạn sẽ đặt tên tiếp theo() chức năng của bạn với ma thuật "__" tiền tố & tên postfix. Một tùy chọn đơn giản là thêm:

%rename(__next__) MyStructIter::next; 
Các vấn đề liên quan