Cách ly các mô-đun mở rộng¶
Ai nên đọc cái này¶
Hướng dẫn này được viết cho những người bảo trì tiện ích mở rộng C-API, những người muốn làm cho tiện ích mở rộng đó an toàn hơn để sử dụng trong các ứng dụng mà chính Python được sử dụng làm thư viện.
Nền¶
Một interpreter là bối cảnh mà mã Python chạy. Nó chứa cấu hình (ví dụ: đường dẫn nhập) và trạng thái thời gian chạy (ví dụ: tập hợp các mô-đun đã nhập).
Python hỗ trợ chạy nhiều trình thông dịch trong một tiến trình. Có hai trường hợp cần cân nhắc—người dùng có thể chạy trình thông dịch:
theo trình tự, với một số chu kỳ
Py_InitializeEx()/Py_FinalizeEx()vàsong song, quản lý "thông dịch viên phụ" bằng
Py_NewInterpreter()/Py_EndInterpreter().
Cả hai trường hợp (và sự kết hợp của chúng) sẽ hữu ích nhất khi nhúng Python vào thư viện. Các thư viện nói chung không nên đưa ra các giả định về ứng dụng sử dụng chúng, bao gồm cả việc giả định một "trình thông dịch Python chính" trong toàn bộ quy trình.
Về mặt lịch sử, các mô-đun mở rộng Python không xử lý tốt trường hợp sử dụng này. Nhiều mô-đun mở rộng (và thậm chí một số mô-đun stdlib) sử dụng trạng thái toàn cục per-process, vì các biến C static cực kỳ dễ sử dụng. Do đó, dữ liệu dành riêng cho trình thông dịch sẽ được chia sẻ giữa các trình thông dịch. Trừ khi nhà phát triển tiện ích mở rộng cẩn thận, rất dễ xảy ra các trường hợp nguy hiểm dẫn đến sự cố khi một mô-đun được tải vào nhiều trình thông dịch trong cùng một quy trình.
Thật không may, trạng thái per-interpreter không dễ đạt được. Các tác giả tiện ích mở rộng có xu hướng không lưu ý đến nhiều trình thông dịch khi phát triển và việc kiểm tra hành vi hiện rất phức tạp.
Nhập trạng thái trên mỗi mô-đun¶
Thay vì tập trung vào trạng thái của mỗi người phiên dịch, C API của Python đang phát triển để hỗ trợ tốt hơn trạng thái per-module chi tiết hơn. Điều này có nghĩa là dữ liệu cấp C phải được đính kèm vào module object. Mỗi trình thông dịch tạo đối tượng mô-đun riêng, giữ dữ liệu riêng biệt. Để kiểm tra tính cách ly, nhiều đối tượng mô-đun tương ứng với một phần mở rộng thậm chí có thể được tải trong một trình thông dịch duy nhất.
Trạng thái trên mỗi mô-đun cung cấp một cách dễ dàng để suy nghĩ về quyền sở hữu tài nguyên và thời gian tồn tại: mô-đun mở rộng sẽ khởi tạo khi một đối tượng mô-đun được tạo và dọn sạch khi nó được giải phóng. Về mặt này, một mô-đun cũng giống như bất kỳ PyObject* nào khác; không có câu lệnh "tắt phiên dịch" nào để suy nghĩ—hoặc quên—về.
Lưu ý rằng có các trường hợp sử dụng cho các loại "toàn cầu" khác nhau: trạng thái mỗi tiến trình, mỗi trình thông dịch, mỗi luồng hoặc mỗi tác vụ. Với trạng thái trên mỗi mô-đun làm mặc định, những điều này vẫn có thể thực hiện được nhưng bạn nên coi chúng là những trường hợp ngoại lệ: nếu cần, bạn nên chăm sóc và kiểm tra thêm cho chúng. (Lưu ý rằng hướng dẫn này không bao gồm chúng.)
Đối tượng mô-đun biệt lập¶
Điểm mấu chốt cần ghi nhớ khi phát triển một mô-đun mở rộng là một số đối tượng mô-đun có thể được tạo từ một thư viện dùng chung. Ví dụ:
>>> nhập hệ thống
>>> nhập binascii
>>> old_binascii = binascii
>>> del sys.modules['binascii']
>>> nhập binascii # create một đối tượng mô-đun mới
>>> old_binascii == binascii
sai
Theo nguyên tắc chung, hai mô-đun phải hoàn toàn độc lập. Tất cả các đối tượng và trạng thái cụ thể của mô-đun phải được gói gọn trong đối tượng mô-đun, không được chia sẻ với các đối tượng mô-đun khác và được dọn sạch khi đối tượng mô-đun bị hủy phân bổ. Vì đây chỉ là quy tắc chung nên có thể xảy ra trường hợp ngoại lệ (xem Managing Global State), nhưng họ sẽ cần suy nghĩ và chú ý nhiều hơn đến các trường hợp đặc biệt.
Mặc dù một số mô-đun có thể thực hiện với các hạn chế ít nghiêm ngặt hơn, nhưng các mô-đun riêng biệt giúp dễ dàng đặt ra các kỳ vọng và hướng dẫn rõ ràng phù hợp với nhiều trường hợp sử dụng khác nhau.
Trường hợp cạnh đáng ngạc nhiên¶
Lưu ý rằng các mô-đun riêng biệt sẽ tạo ra một số trường hợp đáng ngạc nhiên. Đáng chú ý nhất là mỗi đối tượng mô-đun thường sẽ không chia sẻ các lớp và ngoại lệ của nó với các mô-đun tương tự khác. Tiếp tục từ example above, lưu ý rằng old_binascii.Error và binascii.Error là các đối tượng riêng biệt. Trong đoạn mã sau, ngoại lệ là not:
>>> old_binascii.Error == binascii.Error
sai
>>> thử:
... old_binascii.unhexlify(b'qwertyuiop')
... ngoại trừ binascii.Error:
... in('boo')
...
Traceback (cuộc gọi gần đây nhất):
Tệp "<stdin>", dòng 2, trong <module>
binascii.Error: Tìm thấy chữ số không phải thập lục phân
Điều này được mong đợi. Lưu ý rằng các mô-đun Python thuần túy hoạt động theo cách tương tự: đó là một phần cách hoạt động của Python.
Mục tiêu là làm cho các mô-đun mở rộng trở nên an toàn ở cấp độ C chứ không phải để khiến các bản hack hoạt động một cách trực quan. Đột biến sys.modules "thủ công" được tính là hack.
Làm cho mô-đun an toàn với nhiều trình thông dịch¶
Quản lý trạng thái toàn cầu¶
Đôi khi, trạng thái được liên kết với mô-đun Python không dành riêng cho mô-đun đó mà dành cho toàn bộ quá trình (hoặc thứ gì đó "toàn cầu hơn" so với mô-đun). Ví dụ:
Mô-đun
readlinequản lý thiết bị đầu cuối the.Một mô-đun chạy trên bảng mạch muốn điều khiển the trên bo mạch LED.
Trong những trường hợp này, mô-đun Python sẽ cung cấp access cho trạng thái toàn cục, thay vì own. Nếu có thể, hãy viết mô-đun sao cho nhiều bản sao của nó có thể truy cập trạng thái một cách độc lập (cùng với các thư viện khác, cho dù dành cho Python hay các ngôn ngữ khác). Nếu điều đó là không thể, hãy xem xét việc khóa rõ ràng.
Nếu cần sử dụng trạng thái toàn cục của quy trình, cách đơn giản nhất để tránh sự cố với nhiều trình thông dịch là ngăn chặn rõ ràng việc một mô-đun được tải nhiều lần trong mỗi quy trình—xem Chọn không tham gia: Giới hạn ở một đối tượng mô-đun cho mỗi quy trình.
Quản lý trạng thái trên mỗi mô-đun¶
Để sử dụng trạng thái trên mỗi mô-đun, hãy sử dụng multi-phase extension module initialization. Điều này báo hiệu rằng mô-đun của bạn hỗ trợ nhiều trình thông dịch một cách chính xác.
Đặt PyModuleDef.m_size thành số dương để yêu cầu nhiều byte lưu trữ cục bộ cho mô-đun. Thông thường, điều này sẽ được đặt thành kích thước của một số struct dành riêng cho mô-đun, có thể lưu trữ tất cả trạng thái cấp C của mô-đun. Đặc biệt, đó là nơi bạn nên đặt con trỏ tới các lớp (bao gồm các ngoại lệ, nhưng loại trừ các kiểu tĩnh) và cài đặt (ví dụ: field_size_limit của csv) mà mã C cần để hoạt động.
Ghi chú
Một tùy chọn khác là lưu trữ trạng thái trong __dict__ của mô-đun, nhưng bạn phải tránh gặp sự cố khi người dùng sửa đổi __dict__ từ mã Python. Điều này thường có nghĩa là kiểm tra lỗi và loại ở cấp độ C, điều này rất dễ xảy ra sai sót và khó kiểm tra đầy đủ.
Tuy nhiên, nếu trạng thái mô-đun không cần thiết trong mã C thì chỉ nên lưu trữ nó trong __dict__.
Nếu trạng thái mô-đun bao gồm các con trỏ PyObject, đối tượng mô-đun phải chứa các tham chiếu đến các đối tượng đó và triển khai các hook cấp mô-đun m_traverse, m_clear và m_free. Chúng hoạt động giống như tp_traverse, tp_clear và tp_free của một lớp. Việc thêm chúng sẽ đòi hỏi một số công việc và làm cho mã dài hơn; đây là giá cho các mô-đun có thể được dỡ xuống một cách sạch sẽ.
Ví dụ về mô-đun có trạng thái trên mỗi mô-đun hiện có sẵn dưới dạng xxlimited; ví dụ khởi tạo mô-đun được hiển thị ở cuối tệp.
Chọn không tham gia: Giới hạn ở một đối tượng mô-đun cho mỗi quy trình¶
Một tín hiệu PyModuleDef.m_size không âm cho biết một mô-đun hỗ trợ nhiều trình thông dịch một cách chính xác. Nếu điều này chưa xảy ra với mô-đun của bạn, bạn có thể làm cho mô-đun của mình chỉ có thể tải một cách rõ ràng một lần cho mỗi quy trình. Ví dụ:
// Cờ toàn tiến trình
int tĩnh được tải = 0;
// Mutex để cung cấp sự an toàn cho luồng (chỉ cần cho Python có luồng tự do)
tĩnh PyMutex modinit_mutex = {0};
int tĩnh
exec_module(mô-đun PyObject*)
{
PyMutex_Lock(&modinit_mutex);
nếu (đã tải) {
PyMutex_Unlock(&modinit_mutex);
PyErr_SetString(PyExc_ImportError,
"không thể tải mô-đun nhiều lần cho mỗi quy trình");
trả về -1;
}
đã tải = 1;
PyMutex_Unlock(&modinit_mutex);
// ... phần còn lại của quá trình khởi tạo
}
Nếu chức năng PyModuleDef.m_clear của mô-đun của bạn có thể chuẩn bị cho việc khởi tạo lại trong tương lai thì chức năng này sẽ xóa cờ loaded. Trong trường hợp này, mô-đun của bạn sẽ không hỗ trợ nhiều phiên bản concurrently hiện có, nhưng chẳng hạn, nó sẽ hỗ trợ tải sau khi tắt thời gian chạy Python (Py_FinalizeEx()) và khởi tạo lại (Py_Initialize()).
Truy cập trạng thái mô-đun từ các chức năng¶
Việc truy cập trạng thái từ các hàm cấp mô-đun rất đơn giản. Các hàm lấy đối tượng mô-đun làm đối số đầu tiên của chúng; để trích xuất trạng thái, bạn có thể sử dụng PyModule_GetState:
PyObject tĩnh *
func(PyObject *module, PyObject *args)
{
my_struct *state = (my_struct*)PyModule_GetState(module);
nếu (trạng thái == NULL) {
trả về NULL;
}
// ... phần còn lại của logic
}
Ghi chú
PyModule_GetState có thể trả về NULL mà không đặt ngoại lệ nếu không có trạng thái mô-đun, tức là PyModuleDef.m_size bằng 0. Trong mô-đun của riêng bạn, bạn có quyền kiểm soát m_size, vì vậy điều này rất dễ ngăn chặn.
Các loại đống¶
Theo truyền thống, các kiểu được xác định trong mã C là static; nghĩa là các cấu trúc static PyTypeObject được xác định trực tiếp trong mã và được khởi tạo bằng PyType_Ready().
Những loại như vậy nhất thiết phải được chia sẻ trong suốt quá trình. Việc chia sẻ chúng giữa các đối tượng mô-đun đòi hỏi phải chú ý đến bất kỳ trạng thái nào mà chúng sở hữu hoặc truy cập. Để hạn chế các sự cố có thể xảy ra, các loại tĩnh là bất biến ở cấp độ Python: ví dụ: bạn không thể đặt str.myattribute = 123.
Chia sẻ các đối tượng thực sự bất biến giữa các trình thông dịch là được, miễn là chúng không cung cấp quyền truy cập vào các đối tượng có thể thay đổi. Tuy nhiên, trong CPython, mọi đối tượng Python đều có chi tiết triển khai có thể thay đổi: số lượng tham chiếu. Những thay đổi về số tiền hoàn lại được bảo vệ bởi GIL. Do đó, mã chia sẻ bất kỳ đối tượng Python nào giữa các trình thông dịch hoàn toàn phụ thuộc vào GIL hiện tại trên toàn quy trình của CPython.
Bởi vì chúng là bất biến và có tính toàn cục, nên các kiểu tĩnh không thể truy cập trạng thái mô-đun "của chúng". Nếu bất kỳ phương thức nào thuộc loại như vậy yêu cầu quyền truy cập vào trạng thái mô-đun thì loại đó phải được chuyển đổi thành heap-allocated type hoặc viết tắt là heap type. Chúng tương ứng chặt chẽ hơn với các lớp được tạo bởi câu lệnh class của Python.
Đối với các mô-đun mới, việc sử dụng các loại heap theo mặc định là một nguyên tắc chung.
Thay đổi loại tĩnh thành loại heap¶
Loại tĩnh có thể được chuyển đổi thành loại vùng heap, nhưng lưu ý rằng loại vùng heap API không được thiết kế để chuyển đổi "không mất dữ liệu" từ loại tĩnh—nghĩa là tạo ra một loại hoạt động chính xác như một loại tĩnh nhất định. Vì vậy, khi viết lại định nghĩa lớp trong API mới, bạn có thể vô tình thay đổi một số chi tiết (ví dụ: khả năng chọn hoặc vị trí kế thừa). Luôn kiểm tra các chi tiết quan trọng đối với bạn.
Đặc biệt hãy chú ý đến hai điểm sau đây (nhưng lưu ý rằng đây không phải là danh sách đầy đủ):
Không giống như các kiểu tĩnh, các đối tượng kiểu heap có thể thay đổi theo mặc định. Sử dụng cờ
Py_TPFLAGS_IMMUTABLETYPEđể ngăn chặn khả năng thay đổi.Các loại heap kế thừa
tp_newtheo mặc định, do đó có thể khởi tạo chúng từ mã Python. Bạn có thể ngăn chặn điều này bằng cờPy_TPFLAGS_DISALLOW_INSTANTIATION.
Xác định loại Heap¶
Các loại heap có thể được tạo bằng cách điền vào cấu trúc PyType_Spec, mô tả hoặc "bản thiết kế" của một lớp và gọi PyType_FromModuleAndSpec() để xây dựng một đối tượng lớp mới.
Ghi chú
Các hàm khác, như PyType_FromSpec(), cũng có thể tạo các loại heap, nhưng PyType_FromModuleAndSpec() liên kết mô-đun với lớp, cho phép truy cập vào trạng thái mô-đun từ các phương thức.
Lớp này thường phải được lưu trữ ở trạng thái mô-đun both (để truy cập an toàn từ C) và __dict__ của mô-đun (để truy cập từ mã Python).
Giao thức thu gom rác¶
Các thể hiện của kiểu heap giữ một tham chiếu đến kiểu của chúng. Điều này đảm bảo rằng loại này không bị hủy trước khi tất cả các phiên bản của nó bị phá hủy, nhưng có thể dẫn đến các chu trình tham chiếu cần được trình thu gom rác phá vỡ.
Để tránh rò rỉ bộ nhớ, các phiên bản của loại heap phải triển khai giao thức thu gom rác. Nghĩa là, các loại heap nên:
Có cờ
Py_TPFLAGS_HAVE_GC.Xác định hàm duyệt bằng cách sử dụng
Py_tp_traverse, hàm này sẽ truy cập loại (ví dụ: sử dụngPy_VISIT(Py_TYPE(self))).
Vui lòng tham khảo tài liệu của Py_TPFLAGS_HAVE_GC và tp_traverse để biết thêm những cân nhắc.
API để xác định các loại vùng heap phát triển một cách tự nhiên, khiến nó hơi khó sử dụng ở trạng thái hiện tại. Các phần sau đây sẽ hướng dẫn bạn các vấn đề thường gặp.
tp_traverse trong Python 3.8 trở xuống¶
Yêu cầu truy cập loại từ tp_traverse đã được thêm vào Python 3.9. Nếu bạn hỗ trợ Python 3.8 trở xuống thì hàm traverse phải not truy cập vào kiểu nên phải phức tạp hơn:
int tĩnh my_traverse(PyObject *self, visitproc visit, void *arg)
{
nếu (Py_Version >= 0x03090000) {
Py_VISIT(Py_TYPE(tự));
}
trả về 0;
}
Thật không may, Py_Version chỉ được thêm vào Python 3.11. Để thay thế, hãy sử dụng:
PY_VERSION_HEX, nếu không sử dụng ABI ổn định, hoặcsys.version_info(thông quaPySys_GetObject()vàPyArg_ParseTuple()).
Ủy quyền tp_traverse¶
Nếu hàm duyệt của bạn ủy quyền cho tp_traverse của lớp cơ sở của nó (hoặc loại khác), hãy đảm bảo rằng Py_TYPE(self) chỉ được truy cập một lần. Lưu ý rằng chỉ có loại heap mới được phép truy cập loại trong tp_traverse.
Ví dụ: nếu chức năng di chuyển ngang của bạn bao gồm:
base->tp_traverse (tự, truy cập, arg)
...và base có thể là loại tĩnh, thì nó cũng phải bao gồm:
if (base->tp_flags & Py_TPFLAGS_HEAPTYPE) {
// tp_traverse của loại heap đã truy cập Py_TYPE(self)
} khác {
nếu (Py_Version >= 0x03090000) {
Py_VISIT(Py_TYPE(tự));
}
}
Không cần thiết phải xử lý số tham chiếu của loại trong tp_new và tp_clear.
Xác định tp_dealloc¶
Nếu loại của bạn có chức năng tp_dealloc tùy chỉnh, nó cần:
gọi
PyObject_GC_UnTrack()trước khi bất kỳ trường nào bị vô hiệu vàgiảm số lượng tham chiếu của loại.
Để giữ cho loại hợp lệ trong khi tp_free được gọi, số lần đếm lại của loại đó cần phải giảm đi. after phiên bản sẽ bị hủy phân bổ. Ví dụ:
khoảng trống tĩnh my_dealloc(PyObject *self)
{
PyObject_GC_UnTrack(tự);
...
PyTypeObject *type = Py_TYPE(self);
gõ->tp_free(tự);
Py_DECREF(loại);
}
Hàm tp_dealloc mặc định thực hiện điều này, vì vậy nếu loại của bạn not ghi đè tp_dealloc thì bạn không cần thêm nó.
Không ghi đè tp_free¶
Khe tp_free của loại heap phải được đặt thành PyObject_GC_Del(). Đây là mặc định; đừng ghi đè lên nó.
Tránh PyObject_New¶
Các đối tượng được theo dõi bởi GC cần được phân bổ bằng cách sử dụng các hàm nhận biết GC.
Nếu bạn sử dụng PyObject_New() hoặc PyObject_NewVar():
Nhận và gọi khe
tp_alloccủa loại, nếu có thể. Tức là thay thếTYPE *o = PyObject_New(TYPE, typeobj)bằng:TYPE *o = typeobj->tp_alloc(typeobj, 0);
Thay thế
o = PyObject_NewVar(TYPE, typeobj, size)bằng số tương tự nhưng sử dụng kích thước thay vì 0.Nếu không thể thực hiện được điều trên (ví dụ: bên trong
tp_alloctùy chỉnh), hãy gọiPyObject_GC_New()hoặcPyObject_GC_NewVar():TYPE *o = PyObject_GC_New(TYPE, typeobj); TYPE *o = PyObject_GC_NewVar(TYPE, typeobj, kích thước);
Truy cập trạng thái mô-đun từ các lớp¶
Nếu bạn có một đối tượng loại được xác định bằng PyType_FromModuleAndSpec(), bạn có thể gọi PyType_GetModule() để lấy mô-đun liên quan và sau đó gọi PyModule_GetState() để lấy trạng thái của mô-đun.
Để lưu một số mã soạn sẵn xử lý lỗi tẻ nhạt, bạn có thể kết hợp hai bước này với PyType_GetModuleState(), dẫn đến:
my_struct *state = (my_struct*)PyType_GetModuleState(loại);
nếu (trạng thái == NULL) {
trả lại NULL;
}
Truy cập trạng thái mô-đun từ các phương thức thông thường¶
Việc truy cập trạng thái cấp mô-đun từ các phương thức của một lớp có phần phức tạp hơn, nhưng có thể thực hiện được nhờ API được giới thiệu trong Python 3.9. Để có được trạng thái, trước tiên bạn cần lấy defining class, sau đó lấy trạng thái mô-đun từ nó.
Rào cản lớn nhất là nhận được the class a method was defined in hay gọi tắt là "lớp xác định" của phương thức đó. Lớp xác định có thể có tham chiếu đến mô-đun mà nó là một phần.
Đừng nhầm lẫn lớp định nghĩa với Py_TYPE(self). Nếu phương thức được gọi trên subclass thuộc loại của bạn, Py_TYPE(self) sẽ tham chiếu đến lớp con đó, lớp này có thể được định nghĩa trong mô-đun khác với mô-đun của bạn.
Ghi chú
Mã Python sau đây có thể minh họa khái niệm này. Base.get_defining_class trả về Base ngay cả khi type(self) == Sub:
lớp cơ sở:
chắc chắn get_type_of_self(self):
kiểu trả về (tự)
def get_defining_class(self):
trở về __lớp__
lớp phụ (Cơ sở):
vượt qua
Để một phương thức có được "lớp xác định" của nó, nó phải sử dụng METH_METHOD | METH_FASTCALL | METH_KEYWORDS calling convention và chữ ký PyCMethod tương ứng:
PyObject *PyCPhương thức(
PyObject *self, // đối tượng mà phương thức được gọi
PyTypeObject *defining_class, // định nghĩa lớp
PyObject *const *args, // C mảng đối số
Py_ssize_t nargs, // độ dài của "args"
PyObject *kwnames) // NULL hoặc mệnh lệnh đối số từ khóa
Khi bạn có lớp xác định, hãy gọi PyType_GetModuleState() để biết trạng thái của mô-đun liên quan.
Ví dụ:
PyObject tĩnh *
example_method(PyObject *self,
PyTypeObject *định nghĩa_class,
PyObject *const *args,
Py_ssize_t càu nhàu,
PyObject *kwname)
{
my_struct *state = (my_struct*)PyType_GetModuleState(defining_class);
nếu (trạng thái == NULL) {
trả lại NULL;
}
... // phần còn lại của logic
}
PyDoc_STRVAR(example_method_doc, "...");
PyMethodDef tĩnh my_methods[] = {
{"phương thức ví dụ",
(PyCFunction)(void(*)(void))example_method,
METH_METHOD|METH_FASTCALL|METH_KEYWORDS,
example_method_doc}
{NULL},
}
Truy cập trạng thái mô-đun từ các phương thức slot, Getters và Setters¶
Ghi chú
Đây là tính năng mới trong Python 3.11.
Các phương thức slot—tương đương C nhanh cho các phương thức đặc biệt, chẳng hạn như nb_add cho __add__ hoặc tp_new để khởi tạo—có một API rất đơn giản không cho phép chuyển vào lớp xác định, không giống như PyCMethod. Điều tương tự cũng xảy ra với getters và setters được xác định bằng PyGetSetDef.
Để truy cập trạng thái mô-đun trong những trường hợp này, hãy sử dụng hàm PyType_GetModuleByDef() và chuyển vào định nghĩa mô-đun. Khi bạn có mô-đun, hãy gọi PyModule_GetState() để nhận trạng thái:
PyObject *mô-đun = PyType_GetModuleByDef(Py_TYPE(self), &module_def);
my_struct *state = (my_struct*)PyModule_GetState(module);
nếu (trạng thái == NULL) {
trả lại NULL;
}
PyType_GetModuleByDef() hoạt động bằng cách tìm kiếm method resolution order (tức là tất cả các siêu lớp) để tìm siêu lớp đầu tiên có mô-đun tương ứng.
Ghi chú
Trong những trường hợp rất đặc biệt (chuỗi kế thừa bao trùm nhiều mô-đun được tạo từ cùng một định nghĩa), PyType_GetModuleByDef() có thể không trả về mô-đun của lớp xác định thực sự. Tuy nhiên, nó sẽ luôn trả về một mô-đun có cùng định nghĩa, đảm bảo bố cục bộ nhớ C tương thích.
Tuổi thọ của trạng thái mô-đun¶
Khi một đối tượng mô-đun được thu gom rác, trạng thái mô-đun của nó sẽ được giải phóng. Đối với mỗi con trỏ tới (một phần) trạng thái mô-đun, bạn phải giữ một tham chiếu đến đối tượng mô-đun.
Thông thường, đây không phải là vấn đề vì các loại được tạo bằng PyType_FromModuleAndSpec() và các phiên bản của chúng có tham chiếu đến mô-đun. Tuy nhiên, bạn phải cẩn thận trong việc tính tham chiếu khi tham chiếu trạng thái mô-đun từ những nơi khác, chẳng hạn như lệnh gọi lại cho các thư viện bên ngoài.
Các vấn đề mở¶
Một số vấn đề xung quanh trạng thái mỗi mô-đun và loại vùng nhớ heap vẫn chưa được giải quyết.
Các cuộc thảo luận về việc cải thiện tình hình được tổ chức tốt nhất trên discuss forum under c-api tag.
Phạm vi mỗi lớp¶
Hiện tại (kể từ Python 3.11) không thể gắn trạng thái vào từng types mà không dựa vào chi tiết triển khai CPython (điều này có thể thay đổi trong tương lai—có lẽ, trớ trêu thay, lại cho phép một giải pháp thích hợp cho phạm vi mỗi lớp).
Chuyển đổi không mất dữ liệu sang các loại Heap¶
Loại heap API không được thiết kế để chuyển đổi "không mất dữ liệu" từ các loại tĩnh; nghĩa là tạo một loại hoạt động chính xác như một loại tĩnh nhất định.