3. Xác định các loại tiện ích mở rộng: Các chủ đề khác nhau¶
Phần này nhằm mục đích giới thiệu nhanh về các loại phương pháp khác nhau mà bạn có thể triển khai và chức năng của chúng.
Đây là định nghĩa của PyTypeObject, với một số trường chỉ được sử dụng trong debug builds bị bỏ qua:
typedef cấu trúc _typeobject {
PyObject_VAR_HEAD
const char *tp_name; /* Để in, ở định dạng "<module>.<name>" */
Py_ssize_t tp_basicsize, tp_itemsize; /* Để phân bổ */
/* Các phương thức thực hiện các thao tác chuẩn */
hàm hủy tp_dealloc;
Py_ssize_t tp_vectorcall_offset;
getattrfunc tp_getattr;
setattrfunc tp_setattr;
PyAsyncMethods *tp_as_async; /* trước đây gọi là tp_compare (Python 2)
hoặc tp_reserved (Python 3) */
reprfunc tp_repr;
/* Bộ phương thức cho các lớp tiêu chuẩn */
Phương thức PyNumber *tp_as_number;
Phương thức PySequence *tp_as_sequence;
Phương thức PyMapping *tp_as_mapping;
/* Các thao tác tiêu chuẩn khác (ở đây để tương thích nhị phân) */
hashfunc tp_hash;
ternaryfunc tp_call;
reprfunc tp_str;
getattrofunc tp_getattro;
setattrofunc tp_setattro;
/* Hàm truy cập đối tượng dưới dạng bộ đệm đầu vào/đầu ra */
PyBufferProcs *tp_as_buffer;
/* Cờ để xác định sự hiện diện của các tính năng tùy chọn/mở rộng */
tp_flags dài không dấu;
const char *tp_doc; /* Chuỗi tài liệu */
/* Ý nghĩa được gán trong phiên bản 2.0 */
/*gọi hàm cho tất cả các đối tượng có thể truy cập được */
traverseproc tp_traverse;
/*xóa tham chiếu đến các đối tượng được chứa */
yêu cầu tp_clear;
/* Ý nghĩa được gán trong phiên bản 2.1 */
/* so sánh phong phú */
richcmpfunc tp_richcompare;
/* trình kích hoạt tham chiếu yếu */
Py_ssize_t tp_weaklistoffset;
/* Các vòng lặp */
getiterfunc tp_iter;
iternextfunc tp_iternext;
/* Bộ mô tả thuộc tính và nội dung phân lớp */
PyMethodDef *tp_methods;
PyMemberDef *tp_members;
PyGetSetDef *tp_getset;
// Tham chiếu mạnh trên kiểu heap, tham chiếu mượn trên kiểu tĩnh
PyTypeObject *tp_base;
PyObject *tp_dict;
giải mã tp_descr_get;
giải mã tp_descr_set;
Py_ssize_t tp_dictoffset;
initproc tp_init;
phân bổ tp_alloc;
newfunc tp_new;
freefunc tp_free; /* Thủ tục bộ nhớ trống cấp độ thấp */
yêu cầu tp_is_gc; /* Dành cho PyObject_IS_GC */
PyObject *tp_base;
Thứ tự phân giải phương thức PyObject *tp_mro; /* */
PyObject *tp_cache; /* không còn được sử dụng */
void *tp_subclasses; /* đối với các kiểu dựng sẵn tĩnh, đây là một chỉ mục */
PyObject *tp_weaklist; /* không được sử dụng cho các kiểu dựng sẵn tĩnh */
hàm hủy tp_del;
/* Nhập thẻ phiên bản bộ đệm thuộc tính. Đã thêm vào phiên bản 2.6.
* Nếu bằng 0, bộ đệm không hợp lệ và phải được khởi tạo.
*/
unsigned int tp_version_tag;
hàm hủy tp_finalize;
vectorcallfunc tp_vectorcall;
/* bitset mà người theo dõi kiểu quan tâm đến kiểu này */
ký tự không dấu tp_watched;
/* Số lượng giá trị tp_version_tag được sử dụng.
* Đặt thành _Py_ATTR_CACHE_UNUSED nếu bộ đệm thuộc tính là
* bị vô hiệu hóa đối với loại này (ví dụ: do các mục MRO tùy chỉnh).
* Mặt khác, giới hạn ở MAX_VERSIONS_PER_CLASS (được xác định ở nơi khác).
*/
uint16_t tp_versions_used;
} PyTypeObject;
Bây giờ đó là một phương pháp lot. Tuy nhiên, đừng quá lo lắng -- nếu bạn có một loại bạn muốn xác định, rất có thể bạn sẽ chỉ triển khai một số loại trong số này.
Như bạn có thể mong đợi bây giờ, chúng ta sẽ xem xét vấn đề này và cung cấp thêm thông tin về các trình xử lý khác nhau. Chúng tôi sẽ không đi theo thứ tự chúng được xác định trong cấu trúc, bởi vì có rất nhiều dữ liệu lịch sử ảnh hưởng đến thứ tự của các trường. Thông thường, dễ dàng nhất là tìm một ví dụ bao gồm các trường bạn cần và sau đó thay đổi các giá trị cho phù hợp với loại mới của bạn.
const char *tp_name; /* Để in */
Tên của loại -- như đã đề cập ở chương trước, tên này sẽ xuất hiện ở nhiều nơi khác nhau, gần như hoàn toàn nhằm mục đích chẩn đoán. Hãy cố gắng chọn thứ gì đó sẽ hữu ích trong tình huống như vậy!
Py_ssize_t tp_basicsize, tp_itemsize; /* Để phân bổ */
Các trường này cho bộ thực thi biết lượng bộ nhớ cần phân bổ khi các đối tượng mới thuộc loại này được tạo. Python có một số hỗ trợ tích hợp sẵn cho các cấu trúc có độ dài thay đổi (ví dụ: chuỗi, bộ dữ liệu), đây là nơi xuất hiện trường tp_itemsize. Vấn đề này sẽ được giải quyết sau.
const char *tp_doc;
Tại đây, bạn có thể đặt một chuỗi (hoặc địa chỉ của nó) mà bạn muốn trả về khi tập lệnh Python tham chiếu obj.__doc__ để truy xuất chuỗi tài liệu.
Bây giờ chúng ta đến với các phương thức kiểu cơ bản -- những phương thức mà hầu hết các kiểu mở rộng sẽ triển khai.
3.1. Quyết toán và hủy phân bổ¶
hàm hủy tp_dealloc;
Hàm này được gọi khi số lượng tham chiếu của phiên bản thuộc loại của bạn giảm xuống 0 và trình thông dịch Python muốn lấy lại nó. Nếu loại của bạn có bộ nhớ cần giải phóng hoặc việc dọn dẹp khác cần thực hiện, bạn có thể đặt nó ở đây. Bản thân đối tượng cũng cần được giải phóng ở đây. Đây là một ví dụ về chức năng này:
khoảng trống tĩnh
newdatatype_dealloc(PyObject *op)
{
newdatatypeobject *self = (newdatatypeobject *) op;
miễn phí (self->obj_UnderlyingDatatypePtr);
Py_TYPE(tự)->tp_free(tự);
}
Nếu loại của bạn hỗ trợ thu gom rác, hàm hủy sẽ gọi PyObject_GC_UnTrack() trước khi xóa bất kỳ trường thành viên nào
khoảng trống tĩnh
newdatatype_dealloc(PyObject *op)
{
newdatatypeobject *self = (newdatatypeobject *) op;
PyObject_GC_UnTrack(op);
Py_CLEAR(self->other_obj);
...
Py_TYPE(tự)->tp_free(tự);
}
Một yêu cầu quan trọng của hàm giải quyết là nó chỉ để lại mọi ngoại lệ đang chờ xử lý. Điều này rất quan trọng vì bộ giải phóng thường được gọi khi trình thông dịch giải phóng ngăn xếp Python; khi ngăn xếp được giải phóng do một ngoại lệ (thay vì trả về thông thường), không có gì được thực hiện để bảo vệ người giải phóng khỏi thấy rằng một ngoại lệ đã được đặt. Bất kỳ hành động nào mà bộ xử lý thực hiện có thể khiến mã Python bổ sung được thực thi đều có thể phát hiện ra rằng một ngoại lệ đã được đặt. Điều này có thể dẫn đến những lỗi sai lệch từ người phiên dịch. Cách thích hợp để bảo vệ chống lại điều này là lưu ngoại lệ đang chờ xử lý trước khi thực hiện hành động không an toàn và khôi phục hành động đó khi thực hiện xong. Điều này có thể được thực hiện bằng cách sử dụng các hàm PyErr_Fetch() và PyErr_Restore()
khoảng trống tĩnh
my_dealloc(PyObject *obj)
{
MyObject *self = (MyObject *) obj;
PyObject *cbresult;
if (self->my_callback != NULL) {
PyObject *err_type, *err_value, *err_traceback;
/* Thao tác này sẽ lưu trạng thái ngoại lệ hiện tại */
PyErr_Fetch(&err_type, &err_value, &err_traceback);
cbresult = PyObject_CallNoArgs(self->my_callback);
nếu (cbresult == NULL) {
PyErr_WriteUnraiseable(self->my_callback);
}
khác {
Py_DECREF(cbresult);
}
/* Thao tác này khôi phục trạng thái ngoại lệ đã lưu */
PyErr_Restore(err_type, err_value, err_traceback);
Py_DECREF(self->my_callback);
}
Py_TYPE(tự)->tp_free(tự);
}
Ghi chú
Có những hạn chế đối với những gì bạn có thể thực hiện một cách an toàn trong chức năng giải quyết. Đầu tiên, nếu loại của bạn hỗ trợ thu thập rác (sử dụng tp_traverse và/hoặc tp_clear), một số thành viên của đối tượng có thể đã bị xóa hoặc hoàn tất vào thời điểm tp_dealloc được gọi. Thứ hai, trong tp_dealloc, đối tượng của bạn ở trạng thái không ổn định: số tham chiếu của nó bằng 0. Bất kỳ lệnh gọi nào tới một đối tượng không tầm thường hoặc API (như trong ví dụ trên) có thể sẽ gọi lại tp_dealloc, gây ra sự cố gấp đôi và gây ra sự cố.
Bắt đầu với Python 3.4, bạn không nên đặt bất kỳ mã hoàn thiện phức tạp nào vào tp_dealloc mà thay vào đó hãy sử dụng phương thức loại tp_finalize mới.
Xem thêm
PEP 442 giải thích sơ đồ quyết toán mới.
3.2. Trình bày đối tượng¶
Trong Python, có hai cách để tạo biểu diễn văn bản của một đối tượng: hàm repr() và hàm str(). (Hàm print() chỉ gọi str().) Cả hai trình xử lý này đều là tùy chọn.
reprfunc tp_repr;
reprfunc tp_str;
Trình xử lý tp_repr sẽ trả về một đối tượng chuỗi chứa biểu diễn của phiên bản mà nó được gọi. Đây là một ví dụ đơn giản:
PyObject tĩnh *
newdatatype_repr(PyObject *op)
{
newdatatypeobject *self = (newdatatypeobject *) op;
trả về PyUnicode_FromFormat("Repr-ified_newdatatype{{size:%d}}",
self->obj_UnderlyingDatatypePtr->size);
}
Nếu không có trình xử lý tp_repr nào được chỉ định, trình thông dịch sẽ cung cấp một biểu diễn sử dụng tp_name của loại và một giá trị nhận dạng duy nhất cho đối tượng.
Trình xử lý tp_str là str(), trình xử lý tp_repr được mô tả ở trên là repr(); nghĩa là, nó được gọi khi mã Python gọi str() trên một phiên bản đối tượng của bạn. Cách triển khai của nó rất giống với hàm tp_repr, nhưng chuỗi kết quả là dành cho con người. Nếu tp_str không được chỉ định, trình xử lý tp_repr sẽ được sử dụng thay thế.
Đây là một ví dụ đơn giản:
PyObject tĩnh *
newdatatype_str(PyObject *op)
{
newdatatypeobject *self = (newdatatypeobject *) op;
trả về PyUnicode_FromFormat("Stringified_newdatatype{{size:%d}}",
self->obj_UnderlyingDatatypePtr->size);
}
3.3. Quản lý thuộc tính¶
Đối với mọi đối tượng có thể hỗ trợ các thuộc tính, loại tương ứng phải cung cấp các hàm kiểm soát cách giải quyết các thuộc tính. Cần phải có một hàm có thể truy xuất các thuộc tính (nếu có được xác định) và một hàm khác để đặt thuộc tính (nếu cho phép đặt thuộc tính). Xóa thuộc tính là trường hợp đặc biệt, trong đó giá trị mới được truyền cho trình xử lý là NULL.
Python hỗ trợ hai cặp trình xử lý thuộc tính; loại hỗ trợ thuộc tính chỉ cần triển khai các chức năng cho một cặp. Sự khác biệt là một cặp lấy tên thuộc tính là char*, trong khi cặp kia chấp nhận PyObject*. Mỗi loại có thể sử dụng cặp nào hợp lý hơn để thuận tiện cho việc triển khai.
getattrfunc tp_getattr; /* char * phiên bản */
setattrfunc tp_setattr;
/* ... */
getattrofunc tp_getattro; /* Phiên bản PyObject * */
setattrofunc tp_setattro;
Nếu việc truy cập các thuộc tính của một đối tượng luôn là một thao tác đơn giản (điều này sẽ được giải thích ngay sau đây), thì có những triển khai chung có thể được sử dụng để cung cấp phiên bản PyObject* của các chức năng quản lý thuộc tính. Nhu cầu thực tế về trình xử lý thuộc tính dành riêng cho từng loại gần như biến mất hoàn toàn kể từ Python 2.2, mặc dù có nhiều ví dụ chưa được cập nhật để sử dụng một số cơ chế chung mới hiện có.
3.3.1. Quản lý thuộc tính chung¶
Hầu hết các loại tiện ích mở rộng chỉ sử dụng thuộc tính simple. Vậy điều gì làm cho các thuộc tính trở nên đơn giản? Chỉ có một vài điều kiện phải được đáp ứng:
Tên của các thuộc tính phải được biết khi
PyType_Ready()được gọi.Không cần xử lý đặc biệt để ghi lại rằng một thuộc tính đã được tra cứu hoặc thiết lập, cũng như không cần thực hiện các hành động dựa trên giá trị.
Lưu ý rằng danh sách này không đặt ra bất kỳ hạn chế nào đối với giá trị của các thuộc tính, thời điểm các giá trị được tính toán hoặc cách lưu trữ dữ liệu liên quan.
Khi PyType_Ready() được gọi, nó sử dụng ba bảng được tham chiếu bởi đối tượng loại để tạo descriptors được đặt trong từ điển của đối tượng loại. Mỗi bộ mô tả kiểm soát quyền truy cập vào một thuộc tính của đối tượng thể hiện. Mỗi bảng là tùy chọn; nếu cả ba đều là NULL, thì các phiên bản của loại sẽ chỉ có các thuộc tính được kế thừa từ loại cơ sở của chúng và cũng phải để lại các trường tp_getattro và tp_setattro NULL, cho phép loại cơ sở xử lý các thuộc tính.
Các bảng được khai báo là ba trường thuộc loại đối tượng:
struct PyMethodDef *tp_methods;
cấu trúc PyMemberDef *tp_members;
cấu trúc PyGetSetDef *tp_getset;
Nếu tp_methods không phải là NULL thì nó phải tham chiếu đến một mảng cấu trúc PyMethodDef. Mỗi mục trong bảng là một thể hiện của cấu trúc này:
typedef cấu trúc PyMethodDef {
const char *ml_name; /* tên phương thức */
PyCFunction ml_meth; /*chức năng triển khai */
int ml_flags; /* cờ */
const char *ml_doc; /* docstring */
} PyMethodDef;
Một mục nhập phải được xác định cho mỗi phương thức được cung cấp bởi loại đó; không cần mục nào cho các phương thức được kế thừa từ loại cơ sở. Cần có một mục bổ sung ở cuối; nó là trọng điểm đánh dấu sự kết thúc của mảng. Trường ml_name của trọng điểm phải là NULL.
Bảng thứ hai được sử dụng để xác định các thuộc tính ánh xạ trực tiếp tới dữ liệu được lưu trữ trong phiên bản. Nhiều loại C nguyên thủy được hỗ trợ và quyền truy cập có thể ở dạng chỉ đọc hoặc đọc-ghi. Các cấu trúc trong bảng được định nghĩa là:
typedef cấu trúc PyMemberDef {
const char *tên;
kiểu int;
int bù đắp;
cờ int;
const char *doc;
} PyMemberDef;
Đối với mỗi mục trong bảng, một descriptor sẽ được xây dựng và thêm vào loại có thể trích xuất một giá trị từ cấu trúc phiên bản. Trường type phải chứa mã loại như Py_T_INT hoặc Py_T_DOUBLE; giá trị sẽ được sử dụng để xác định cách chuyển đổi giá trị Python sang và từ giá trị C. Trường flags được sử dụng để lưu trữ các cờ kiểm soát cách truy cập thuộc tính: bạn có thể đặt nó thành Py_READONLY để ngăn mã Python thiết lập nó.
Một lợi thế thú vị của việc sử dụng bảng tp_members để xây dựng các bộ mô tả được sử dụng trong thời gian chạy là bất kỳ thuộc tính nào được xác định theo cách này đều có thể có chuỗi tài liệu liên quan chỉ bằng cách cung cấp văn bản trong bảng. Một ứng dụng có thể sử dụng tính năng xem xét nội tâm API để truy xuất bộ mô tả từ đối tượng lớp và lấy chuỗi tài liệu bằng thuộc tính __doc__ của nó.
Giống như bảng tp_methods, cần có một mục nhập trọng điểm có giá trị ml_name là NULL.
3.3.2. Quản lý thuộc tính theo loại cụ thể¶
Để đơn giản, chỉ có phiên bản char* sẽ được trình bày ở đây; loại tham số tên là điểm khác biệt duy nhất giữa phiên bản char* và PyObject* của giao diện. Ví dụ này thực hiện tương tự như ví dụ chung ở trên nhưng không sử dụng hỗ trợ chung được thêm vào trong Python 2.2. Nó giải thích cách gọi các hàm xử lý, để nếu thực sự cần mở rộng chức năng của chúng, bạn sẽ hiểu những gì cần phải làm.
Trình xử lý tp_getattr được gọi khi đối tượng yêu cầu tra cứu thuộc tính. Nó được gọi trong các tình huống tương tự khi phương thức __getattr__() của một lớp sẽ được gọi.
Đây là một ví dụ:
PyObject tĩnh *
newdatatype_getattr(PyObject *op, char *name)
{
newdatatypeobject *self = (newdatatypeobject *) op;
if (strcmp(name, "data") == 0) {
trả về PyLong_FromLong(self->data);
}
PyErr_Format(PyExc_AttributionError,
Đối tượng "'%.100s' không có thuộc tính '%.400s'",
Py_TYPE(tự)->tp_name, tên);
trả lại NULL;
}
Trình xử lý tp_setattr được gọi khi phương thức __setattr__() hoặc __delattr__() của một thể hiện lớp sẽ được gọi. Khi cần xóa một thuộc tính, tham số thứ ba sẽ là NULL. Đây là một ví dụ đơn giản đưa ra một ngoại lệ; nếu đây thực sự là tất cả những gì bạn muốn thì trình xử lý tp_setattr sẽ được đặt thành NULL.
int tĩnh
newdatatype_setattr(PyObject *op, char *name, PyObject *v)
{
PyErr_Format(PyExc_RuntimeError, "Thuộc tính chỉ đọc: %s", tên);
trả về -1;
}
3.4. So sánh đối tượng¶
richcmpfunc tp_richcompare;
Trình xử lý tp_richcompare được gọi khi cần so sánh. Nó tương tự như rich comparison methods, như __lt__(), và còn được gọi bằng PyObject_RichCompare() và PyObject_RichCompareBool().
Hàm này được gọi với hai đối tượng Python và toán tử làm đối số, trong đó toán tử là một trong các Py_EQ, Py_NE, Py_LE, Py_GE, Py_LT hoặc Py_GT. Nó nên so sánh hai đối tượng với toán tử đã chỉ định và trả về Py_True hoặc Py_False nếu so sánh thành công, Py_NotImplemented để chỉ ra rằng so sánh không được triển khai và nên thử phương pháp so sánh của đối tượng kia hoặc NULL nếu một ngoại lệ được đặt.
Đây là một cách triển khai mẫu, cho kiểu dữ liệu được coi là bằng nhau nếu kích thước của con trỏ bên trong bằng nhau
PyObject tĩnh *
newdatatype_richcmp(PyObject *lhs, PyObject *rhs, int op)
{
newdatatypeobject *obj1 = (newdatatypeobject *) lhs;
newdatatypeobject *obj2 = (newdatatypeobject *) rhs;
kết quả PyObject *;
int c, size1, size2;
/* mã để đảm bảo rằng cả hai đối số đều cùng loại
kiểu dữ liệu mới bị bỏ qua */
size1 = obj1->obj_UnderlyingDatatypePtr->size;
size2 = obj2->obj_UnderlyingDatatypePtr->size;
chuyển đổi (op) {
trường hợp Py_LT: c = size1 < size2; phá vỡ;
trường hợp Py_LE: c = size1 <= size2; phá vỡ;
trường hợp Py_EQ: c = size1 == size2; phá vỡ;
trường hợp Py_NE: c = size1 != size2; phá vỡ;
trường hợp Py_GT: c = size1 > size2; phá vỡ;
trường hợp Py_GE: c = size1 >= size2; phá vỡ;
}
kết quả = c ? Py_True : Py_False;
trả về Py_NewRef(kết quả);
}
3.5. Hỗ trợ giao thức trừu tượng¶
Python hỗ trợ nhiều 'giao thức' abstract;' các giao diện cụ thể được cung cấp để sử dụng các giao diện này được ghi lại trong Lớp đối tượng trừu tượng.
Một số giao diện trừu tượng này đã được xác định sớm trong quá trình phát triển triển khai Python. Đặc biệt, các giao thức số, ánh xạ và trình tự đã là một phần của Python ngay từ đầu. Các giao thức khác đã được thêm vào theo thời gian. Đối với các giao thức phụ thuộc vào một số quy trình xử lý từ việc triển khai kiểu, các giao thức cũ hơn đã được xác định là các khối trình xử lý tùy chọn được tham chiếu bởi đối tượng kiểu. Đối với các giao thức mới hơn, có các vị trí bổ sung trong đối tượng loại chính, với bit cờ được đặt để cho biết rằng các vị trí đó có mặt và cần được trình thông dịch kiểm tra. (Bit cờ không chỉ ra rằng các giá trị vị trí không phải là NULL. Cờ có thể được đặt để biểu thị sự hiện diện của một vị trí, nhưng một vị trí có thể vẫn chưa được lấp đầy.)
Phương thức PyNumber *tp_as_number;
Phương thức PySequence *tp_as_sequence;
Phương thức PyMapping *tp_as_mapping;
Nếu bạn muốn đối tượng của mình có thể hoạt động giống như một số, một chuỗi hoặc một đối tượng ánh xạ thì bạn đặt địa chỉ của cấu trúc triển khai kiểu C lần lượt là PyNumberMethods, PySequenceMethods hoặc PyMappingMethods. Bạn có thể điền vào cấu trúc này những giá trị thích hợp. Bạn có thể tìm thấy các ví dụ về việc sử dụng từng cái này trong thư mục Objects của bản phân phối nguồn Python.
hashfunc tp_hash;
Hàm này, nếu bạn chọn cung cấp, sẽ trả về số băm cho một phiên bản thuộc loại dữ liệu của bạn. Đây là một ví dụ đơn giản:
Py_hash_t tĩnh
newdatatype_hash(PyObject *op)
{
newdatatypeobject *self = (newdatatypeobject *) op;
Kết quả Py_hash_t;
kết quả = self->some_size + 32767 * self->some_number;
nếu (kết quả == -1) {
kết quả = -2;
}
trả về kết quả;
}
Py_hash_t là loại số nguyên có dấu với chiều rộng thay đổi theo nền tảng. Việc trả về -1 từ tp_hash cho thấy có lỗi, đó là lý do tại sao bạn nên cẩn thận để tránh trả lại nó khi tính toán hàm băm thành công, như đã thấy ở trên.
ternaryfunc tp_call;
Hàm này được gọi khi một phiên bản của loại dữ liệu của bạn được "gọi", ví dụ: nếu obj1 là một phiên bản của loại dữ liệu của bạn và tập lệnh Python chứa obj1('hello') thì trình xử lý tp_call sẽ được gọi.
Hàm này có ba đối số:
self là phiên bản của kiểu dữ liệu là chủ đề của lệnh gọi. Nếu cuộc gọi là
obj1('hello')thì self làobj1.args là một bộ chứa các đối số cho lệnh gọi. Bạn có thể sử dụng
PyArg_ParseTuple()để trích xuất các đối số.kwds là từ điển các đối số từ khóa đã được thông qua. Nếu đây không phải là
NULLvà bạn hỗ trợ các đối số từ khóa, hãy sử dụngPyArg_ParseTupleAndKeywords()để trích xuất các đối số. Nếu bạn không muốn hỗ trợ các đối số từ khóa và đây không phải làNULL, hãy đưa raTypeErrorvới thông báo cho biết rằng các đối số từ khóa không được hỗ trợ.
Đây là cách triển khai đồ chơi tp_call:
PyObject tĩnh *
newdatatype_call(PyObject *op, PyObject *args, PyObject *kwds)
{
newdatatypeobject *self = (newdatatypeobject *) op;
kết quả PyObject *;
const char *arg1;
const char *arg2;
const char *arg3;
if (!PyArg_ParseTuple(args, "sss:call", &arg1, &arg2, &arg3)) {
trả lại NULL;
}
kết quả = PyUnicode_FromFormat(
"Trả về -- giá trị: [%d] arg1: [%s] arg2: [%s] arg3: [%s]\n",
tự->obj_UnderlyingDatatypePtr->kích thước,
arg1, arg2, arg3);
trả về kết quả;
}
/* Các vòng lặp */
getiterfunc tp_iter;
iternextfunc tp_iternext;
Các hàm này cung cấp hỗ trợ cho giao thức iterator. Cả hai trình xử lý đều lấy chính xác một tham số, phiên bản mà chúng được gọi và trả về một tham chiếu mới. Trong trường hợp có lỗi, họ nên đặt ngoại lệ và trả về NULL. tp_iter tương ứng với phương thức __iter__() của Python, trong khi tp_iternext tương ứng với phương thức __next__() của Python.
Bất kỳ đối tượng iterable nào cũng phải triển khai trình xử lý tp_iter, trình xử lý này phải trả về một đối tượng iterator. Ở đây các nguyên tắc tương tự được áp dụng như đối với các lớp Python:
Đối với các bộ sưu tập (chẳng hạn như danh sách và bộ dữ liệu) có thể hỗ trợ nhiều trình vòng lặp độc lập, một trình vòng lặp mới phải được tạo và trả về sau mỗi lệnh gọi tới
tp_iter.Các đối tượng chỉ có thể được lặp lại một lần (thường là do tác dụng phụ của việc lặp lại, chẳng hạn như đối tượng tệp) có thể triển khai
tp_iterbằng cách trả về một tham chiếu mới cho chính chúng -- và do đó cũng nên triển khai trình xử lýtp_iternext.
Bất kỳ đối tượng iterator nào cũng phải triển khai cả tp_iter và tp_iternext. Trình xử lý tp_iter của iterator sẽ trả về một tham chiếu mới cho iterator. Trình xử lý tp_iternext của nó sẽ trả về một tham chiếu mới cho đối tượng tiếp theo trong vòng lặp, nếu có. Nếu quá trình lặp đã kết thúc, tp_iternext có thể trả về NULL mà không đặt ngoại lệ hoặc có thể đặt StopIteration in addition trả về NULL; tránh ngoại lệ có thể mang lại hiệu suất tốt hơn một chút. Nếu xảy ra lỗi thực tế, tp_iternext phải luôn đặt ngoại lệ và trả về NULL.
3.6. Hỗ trợ tham khảo yếu¶
Một trong những mục tiêu của việc triển khai tham chiếu yếu của Python là cho phép mọi loại tham gia vào cơ chế tham chiếu yếu mà không phải chịu chi phí chung đối với các đối tượng quan trọng về hiệu năng (chẳng hạn như số).
Xem thêm
Tài liệu cho mô-đun weakref.
Để một đối tượng có khả năng tham chiếu yếu, loại tiện ích mở rộng phải đặt bit Py_TPFLAGS_MANAGED_WEAKREF của trường tp_flags. Trường tp_weaklistoffset kế thừa phải được giữ nguyên bằng 0.
Cụ thể, đây là cách đối tượng kiểu được khai báo tĩnh:
tĩnh PyTypeObject TrivialType = {
PyVarObject_HEAD_INIT(NULL, 0)
/* ... các thành viên khác được lược bỏ cho ngắn gọn ... */
.tp_flags = Py_TPFLAGS_MANAGED_WEAKREF | ...,
};
Sự bổ sung duy nhất nữa là tp_dealloc cần xóa mọi tham chiếu yếu (bằng cách gọi PyObject_ClearWeakRefs()):
khoảng trống tĩnh
Trivial_dealloc(PyObject *op)
{
/* Xóa các điểm yếu trước khi gọi bất kỳ hàm hủy nào */
PyObject_ClearWeakRefs(op);
/* ... phần còn lại của mã hủy được bỏ qua để cho ngắn gọn ... */
Py_TYPE(op)->tp_free(op);
}
3.7. Thêm đề xuất¶
Để tìm hiểu cách triển khai bất kỳ phương pháp cụ thể nào cho loại dữ liệu mới của bạn, hãy lấy mã nguồn CPython. Đi tới thư mục Objects, sau đó tìm kiếm các tệp nguồn C cho tp_ cộng với chức năng bạn muốn (ví dụ: tp_richcompare). Bạn sẽ tìm thấy các ví dụ về chức năng bạn muốn triển khai.
Khi bạn cần xác minh rằng một đối tượng là một phiên bản cụ thể của loại bạn đang triển khai, hãy sử dụng hàm PyObject_TypeCheck(). Một mẫu sử dụng của nó có thể giống như sau:
if (!PyObject_TypeCheck(some_object, &MyType)) {
PyErr_SetString(PyExc_TypeError, "arg #1 không phải là chuyện hoang đường");
trả lại NULL;
}
Xem thêm
- Tải xuống bản phát hành nguồn CPython.
- Dự án CPython trên GitHub, nơi phát triển mã nguồn CPython.