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__
và __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