2012-07-12 11 views
5

Tôi có một lớp học tương tự như sau:Python bắt buộc đối với C++ nhà khai thác quá tải

class A { 
    vector<double> v; 
    double& x(int i) { return v[2*i]; } 
    double& y(int i) { return v[2*i+1]; } 
    double x(int i) const { return v[2*i]; } 
    double y(int i) const { return v[2*i+1]; } 
} 

Tôi muốn có mã Python công việc sau đây:

a = A() 
a.x[0] = 4 
print a.x[0] 

Tôi đã nghĩ đến việc __setattr____getattr__, nhưng không chắc chắn nếu nó hoạt động. Cách khác là triển khai Python sau đây:

a = A() 
a['x', 0] = 4 
print a['x', 0] 

không tốt như trước, nhưng có thể dễ thực hiện hơn (với __slice__?).

PS. Tôi đang sử dụng ngụm để làm các ràng buộc.

Cảm ơn.

Trả lời

6

Có thể với __getattr__ và tùy chỉnh %MethodCode; Tuy nhiên, có một vài điểm để đi vào xem xét:

  • Một loại/đối tượng trung gian cần phải được tạo ra, như a.x sẽ trả về một đối tượng cung cấp __getitem____setitem__. Cả hai phương pháp đều phải tăng IndexError khi vượt quá giới hạn, vì đây là một phần của giao thức cũ được sử dụng để lặp qua __getitem__; không có nó, một sự cố sẽ xảy ra khi lặp lại trên a.x.
  • Để đảm bảo tuổi thọ của vec-tơ, đối tượng a.x cần duy trì tham chiếu đến đối tượng sở hữu vectơ (a). Xét đoạn mã sau:

    a = A() 
    x = a.x 
    a = None # If 'x' has a reference to 'a.v' and not 'a', then it may have a 
         # dangling reference, as 'a' is refcounted by python, and 'a.v' is 
         # not refcounted. 
    
  • Viết %MethodCode có thể khó khăn, đặc biệt là khi phải quản lý các tính tham khảo trong trường hợp lỗi. Nó đòi hỏi một sự hiểu biết về python C API và SIP.

Đối với một giải pháp thay thế, hãy cân nhắc:

  • Thiết kế bindings python để cung cấp chức năng.
  • Lớp thiết kế trong python để cung cấp giao diện pythonic sử dụng các ràng buộc.

Trong khi phương pháp này có một vài hạn chế, chẳng hạn như mã được tách ra thành nhiều file có thể cần phải được phân phối với thư viện, nó cung cấp một số lợi ích chính:

  • Nó là dễ dàng hơn nhiều để thực hiện một giao diện pythonic trong python so với trong C hoặc giao diện của thư viện tương tác.
  • Hỗ trợ cắt, lặp, vv có thể được triển khai tự nhiên hơn trong python, thay vì phải quản lý nó thông qua API C.
  • Có thể tận dụng bộ thu gom rác của python để quản lý tuổi thọ của bộ nhớ cơ bản.
  • Giao diện pythonic được tách rời khỏi bất kỳ việc triển khai nào đang được sử dụng để cung cấp khả năng tương tác giữa python và C++.Với giao diện liên kết phẳng hơn và đơn giản hơn, việc thay đổi giữa các triển khai, chẳng hạn như Boost.Python và SIP, dễ dàng hơn nhiều.

Đây là hướng dẫn minh họa cách tiếp cận này. Đầu tiên, chúng ta bắt đầu với lớp cơ bản A. Trong ví dụ này, tôi đã cung cấp một hàm tạo sẽ thiết lập một số dữ liệu ban đầu.

a.hpp:

#ifndef A_HPP 
#define A_HPP 

#include <vector> 

class A 
{ 
    std::vector<double> v; 
public: 
    A() { for (int i = 0; i < 6; ++i) v.push_back(i); } 
    double& x(int i)   { return v[2*i];  } 
    double x(int i) const { return v[2*i];  } 
    double& y(int i)   { return v[2*i+1];  } 
    double y(int i) const { return v[2*i+1];  } 
    std::size_t size() const { return v.size()/2; } 
}; 

#endif // A_HPP 

Trước khi làm các ràng buộc, cho phép kiểm tra các giao diện A. Trong khi đó là một giao diện dễ sử dụng trong C++, nó có một số khó khăn trong python:

  • Python không hỗ trợ phương pháp quá tải, và thành ngữ để hỗ trợ quá tải sẽ thất bại khi các loại lập luận/đếm đều giống nhau.
  • Khái niệm về tham chiếu đến dấu hai chấm (nổi bằng Python) khác nhau giữa hai ngôn ngữ. Trong Python, float là một kiểu bất biến, vì vậy giá trị của nó không thể thay đổi được. Ví dụ: trong Python, câu lệnh n = a.x[0] liên kết n để tham chiếu đối tượng float được trả lại từ a.x[0]. Bài tập n = 4 rebinds n để tham chiếu đối tượng int(4); nó không đặt a.x[0] đến 4.
  • __len__ mong đợi int, không phải std::size_t.

Cho phép tạo lớp trung gian cơ bản sẽ giúp đơn giản hóa các ràng buộc.

pya.hpp:

#ifndef PYA_HPP 
#define PYA_HPP 

#include "a.hpp" 

struct PyA: A 
{ 
    double get_x(int i)   { return x(i); } 
    void set_x(int i, double v) { x(i) = v; } 
    double get_y(int i)   { return y(i); } 
    void set_y(int i, double v) { y(i) = v; } 
    int length()     { return size(); } 
}; 

#endif // PYA_HPP 

Tuyệt vời! PyA hiện cung cấp các hàm thành viên không trả về tham chiếu và độ dài được trả lại dưới dạng int. Đây không phải là giao diện tốt nhất, các liên kết được thiết kế để cung cấp chức năng cần thiết, thay vì giao diện mong muốn.

Bây giờ, hãy viết một số ràng buộc đơn giản sẽ tạo lớp A trong mô-đun cexample.

Dưới đây là các ràng buộc trong SIP:

%Module cexample 

class PyA /PyName=A/ 
{ 
%TypeHeaderCode 
#include "pya.hpp" 
%End 
public: 
    double get_x(int); 
    void set_x(int, double); 
    double get_y(int); 
    void set_y(int, double); 
    int __len__(); 
    %MethodCode 
    sipRes = sipCpp->length(); 
    %End 
}; 

Hoặc nếu bạn thích Boost.Python:

#include "pya.hpp" 
#include <boost/python.hpp> 

BOOST_PYTHON_MODULE(cexample) 
{ 
    using namespace boost::python; 
    class_<PyA>("A") 
    .def("get_x", &PyA::get_x ) 
    .def("set_x", &PyA::set_x ) 
    .def("get_y", &PyA::get_y ) 
    .def("set_y", &PyA::set_y ) 
    .def("__len__", &PyA::length) 
    ; 
} 

Do lớp trung gian PyA cả các ràng buộc là khá đơn giản. Ngoài ra, phương pháp này đòi hỏi ít kiến ​​thức về SIP và Python C API vì nó đòi hỏi ít mã hơn trong các khối %MethodCode.

Cuối cùng, tạo example.py mà sẽ cung cấp giao diện pythonic mong muốn:

class A: 
    class __Helper: 
     def __init__(self, data, getter, setter): 
      self.__data = data 
      self.__getter = getter 
      self.__setter = setter 

     def __getitem__(self, index): 
      if len(self) <= index: 
       raise IndexError("index out of range") 
      return self.__getter(index) 

     def __setitem__(self, index, value): 
      if len(self) <= index: 
       raise IndexError("index out of range") 
      self.__setter(index, value) 

     def __len__(self): 
      return len(self.__data) 

    def __init__(self): 
     import cexample 
     a = cexample.A() 
     self.x = A.__Helper(a, a.get_x, a.set_x) 
     self.y = A.__Helper(a, a.get_y, a.set_y) 

Cuối cùng, các ràng buộc cung cấp các chức năng chúng ta cần, và python tạo ra các giao diện chúng ta muốn. Có thể có các ràng buộc cung cấp giao diện; tuy nhiên, điều này có thể đòi hỏi một sự hiểu biết phong phú về sự khác biệt giữa hai ngôn ngữ và việc thực hiện ràng buộc.

>>> from example import A 
>>> a = A() 
>>> for x in a.x: 
... print x 
... 
0.0 
2.0 
4.0 
>>> a.x[0] = 4 
>>> for x in a.x: 
... print x 
... 
4.0 
2.0 
4.0 
>>> x = a.x 
>>> a = None 
>>> print x[0] 
4.0