Xác định các mô-đun mở rộng¶
Tiện ích mở rộng C cho CPython là một thư viện dùng chung (ví dụ: tệp .so trên Linux, .pyd DLL trên Windows), có thể tải vào quy trình Python (ví dụ: nó được biên dịch với cài đặt trình biên dịch tương thích) và xuất initialization function.
Để có thể nhập theo mặc định (nghĩa là theo importlib.machinery.ExtensionFileLoader), thư viện dùng chung phải có sẵn trên sys.path và phải được đặt tên theo tên mô-đun cộng với phần mở rộng được liệt kê trong importlib.machinery.EXTENSION_SUFFIXES.
Ghi chú
Việc xây dựng, đóng gói và phân phối các mô-đun mở rộng được thực hiện tốt nhất bằng các công cụ của bên thứ ba và nằm ngoài phạm vi của tài liệu này. Một công cụ phù hợp là Setuptools, có thể tìm thấy tài liệu tại https://setuptools.pypa.io/en/latest/setuptools.html.
Thông thường, hàm khởi tạo trả về định nghĩa mô-đun được khởi tạo bằng PyModuleDef_Init(). Điều này cho phép chia quá trình tạo thành nhiều giai đoạn:
Trước khi thực thi bất kỳ mã quan trọng nào, Python có thể xác định những khả năng nào mà mô-đun hỗ trợ và nó có thể điều chỉnh môi trường hoặc từ chối tải tiện ích mở rộng không tương thích.
Theo mặc định, Python tự tạo đối tượng mô-đun - nghĩa là, nó thực hiện tương đương với
object.__new__()cho các lớp. Nó cũng đặt các thuộc tính ban đầu như__package__và__loader__.Sau đó, đối tượng mô-đun được khởi tạo bằng mã dành riêng cho tiện ích mở rộng -- tương đương với
__init__()trên các lớp.
Đây được gọi là multi-phase initialization để phân biệt với sơ đồ single-phase initialization cũ (nhưng vẫn được hỗ trợ), trong đó hàm khởi tạo trả về một mô-đun được xây dựng hoàn chỉnh. Xem single-phase-initialization section below để biết chi tiết.
Thay đổi trong phiên bản 3.5: Đã thêm hỗ trợ khởi tạo nhiều pha (PEP 489).
Nhiều phiên bản mô-đun¶
Theo mặc định, các mô-đun mở rộng không phải là mô-đun đơn lẻ. Ví dụ: nếu mục sys.modules bị xóa và mô-đun được nhập lại, một đối tượng mô-đun mới sẽ được tạo và thường được điền bằng các đối tượng kiểu và phương thức mới. Mô-đun cũ có thể được thu gom rác thông thường. Điều này phản ánh hành vi của các mô-đun Python thuần túy.
Các phiên bản mô-đun bổ sung có thể được tạo trong sub-interpreters hoặc sau khi khởi tạo lại thời gian chạy Python (Py_Finalize() và Py_Initialize()). Trong những trường hợp này, việc chia sẻ đối tượng Python giữa các phiên bản mô-đun có thể gây ra sự cố hoặc hành vi không xác định.
Để tránh những vấn đề như vậy, mỗi phiên bản của mô-đun mở rộng phải là isolated: các thay đổi đối với một phiên bản không được ảnh hưởng ngầm đến các phiên bản khác và tất cả trạng thái do mô-đun sở hữu, bao gồm cả các tham chiếu đến đối tượng Python, phải dành riêng cho một phiên bản mô-đun cụ thể. Xem Cách ly các mô-đun mở rộng để biết thêm chi tiết và hướng dẫn thực tế.
Một cách đơn giản hơn để tránh những vấn đề này là raising an error on repeated initialization.
Tất cả các mô-đun dự kiến sẽ hỗ trợ sub-interpreters hoặc báo hiệu rõ ràng việc thiếu hỗ trợ. Điều này thường đạt được bằng cách cách ly hoặc chặn việc khởi tạo lặp lại, như trên. Một mô-đun cũng có thể bị giới hạn ở trình thông dịch chính sử dụng khe Py_mod_multiple_interpreters.
Chức năng khởi tạo¶
Hàm khởi tạo được xác định bởi mô-đun mở rộng có chữ ký sau:
Tên của nó phải là PyInit_<name>, với <name> được thay thế bằng tên của mô-đun.
Đối với các mô-đun chỉ có tên ASCII, thay vào đó, hàm phải được đặt tên là PyInit_<name>, với <name> được thay thế bằng tên của mô-đun. Khi sử dụng Khởi tạo nhiều pha, cho phép tên mô-đun không phải ASCII. Trong trường hợp này, tên hàm khởi tạo là PyInitU_<name>, với <name> được mã hóa bằng mã hóa punycode của Python với dấu gạch ngang được thay thế bằng dấu gạch dưới. Trong Python:
def initfunc_name(tên):
thử:
hậu tố = b'_' + name.encode('ascii')
ngoại trừ UnicodeEncodeError:
hậu tố = b'U_' + name.encode('punycode').replace(b'-', b'_')
trả về b'PyInit' + hậu tố
Bạn nên xác định hàm khởi tạo bằng macro trợ giúp:
-
PyMODINIT_FUNC¶
Khai báo hàm khởi tạo module mở rộng. Vĩ mô này:
chỉ định kiểu trả về PyObject*,
thêm bất kỳ khai báo liên kết đặc biệt nào mà nền tảng yêu cầu và
đối với C++, khai báo hàm là
extern "C".
Ví dụ: một mô-đun có tên spam sẽ được định nghĩa như sau:
cấu trúc tĩnh PyModuleDef spam_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "thư rác",
...
};
PyMODINIT_FUNC
PyInit_spam(void)
{
trả về PyModuleDef_Init(&spam_module);
}
Có thể xuất nhiều mô-đun từ một thư viện dùng chung bằng cách xác định nhiều hàm khởi tạo. Tuy nhiên, việc nhập chúng yêu cầu sử dụng các liên kết tượng trưng hoặc trình nhập tùy chỉnh, vì theo mặc định chỉ tìm thấy hàm tương ứng với tên tệp. Xem phần Multiple modules in one library trong PEP 489 để biết chi tiết.
Hàm khởi tạo thường là mục không phải static duy nhất được xác định trong nguồn C của mô-đun.
Khởi tạo nhiều pha¶
Thông thường, initialization function (PyInit_modulename) trả về một phiên bản PyModuleDef không phải là NULL m_slots. Trước khi được trả về, phiên bản PyModuleDef phải được khởi tạo bằng hàm sau:
-
PyObject *PyModuleDef_Init(PyModuleDef *def)¶
- Một phần của ABI ổn định kể từ phiên bản 3.5.
Đảm bảo định nghĩa mô-đun là một đối tượng Python được khởi tạo đúng cách, báo cáo chính xác loại và số lượng tham chiếu của nó.
Trả lại def đã truyền về
PyObject*hoặcNULLnếu xảy ra lỗi.Việc gọi hàm này là bắt buộc đối với Khởi tạo nhiều pha. Nó không nên được sử dụng trong các bối cảnh khác.
Lưu ý rằng Python giả định rằng cấu trúc
PyModuleDefđược phân bổ tĩnh. Hàm này có thể trả về một tham chiếu mới hoặc một tham chiếu đã mượn; tài liệu tham khảo này không được phát hành.Added in version 3.5.
Khởi tạo một pha kế thừa¶
Chú ý
Khởi tạo một pha là một cơ chế kế thừa để khởi tạo các mô-đun mở rộng, với những hạn chế và lỗi thiết kế đã biết. Thay vào đó, các tác giả mô-đun mở rộng được khuyến khích sử dụng khởi tạo nhiều giai đoạn.
Trong quá trình khởi tạo một pha, initialization function (PyInit_modulename) sẽ tạo, điền và trả về một đối tượng mô-đun. Điều này thường được thực hiện bằng cách sử dụng PyModule_Create() và các hàm như PyModule_AddObjectRef().
Khởi tạo một pha khác với default ở những điểm sau:
Các mô-đun một pha, hay đúng hơn là contain, “singleton”.
Khi mô-đun được khởi tạo lần đầu tiên, Python sẽ lưu nội dung của
__dict__của mô-đun (nghĩa là, thông thường, các chức năng và loại của mô-đun).Đối với các lần nhập tiếp theo, Python không gọi lại hàm khởi tạo. Thay vào đó, nó tạo một đối tượng mô-đun mới với
__dict__mới và sao chép nội dung đã lưu vào đó. Ví dụ: với mô-đun một pha_testsinglephase[1] xác định hàmsumvà một lớp ngoại lệerror:>>> nhập hệ thống >>> nhập _testsinglephase làm một >>> del sys.modules['_testsinglephase'] >>> nhập _testsinglephase thành hai >>> một là hai sai >>> một.__dict__ là hai.__dict__ sai >>> một.sum là hai.sum đúng >>> một.lỗi là hai.lỗi đúng
Hành vi chính xác phải được coi là chi tiết triển khai CPython.
Để giải quyết vấn đề
PyInit_modulenamekhông nhận đối số spec, một số trạng thái của máy nhập sẽ được lưu và áp dụng cho mô-đun phù hợp đầu tiên được tạo trong lệnh gọiPyInit_modulename. Cụ thể, khi một mô-đun phụ được nhập, cơ chế này sẽ thêm tên gói gốc vào tên của mô-đun.Hàm
PyInit_modulenamemột pha sẽ tạo đối tượng mô-đun “của nó” càng sớm càng tốt, trước khi có thể tạo bất kỳ đối tượng mô-đun nào khác.Tên mô-đun không phải ASCII (
PyInitU_modulename) không được hỗ trợ.Các mô-đun một pha hỗ trợ các chức năng tra cứu mô-đun như
PyState_FindModule().