1. Rozszerzanie Pythona za pomocą C lub C++¶
Jest całkiem łatwo dodać nowe wbudowane moduły do Pythona, jeśli znasz się na programowaniu w C. Takie moduły rozszerzające <extension modules> mogą zrobić dwie rzeczy których nie da się zrobić bezpośrednio w Pythonie: mogą wypełnić nowe wbudowane typy przedmiotów i mogą odwołać się do zadań bibliotecznych C i odwołań systemowych.
Aby wspierać rozszerzenia, API Pythona (Application Programmers Interface) określa zbiór funkcji, makropoleceń i zmiennych, które dostarczają dostęp do większości aspektów systemu czasu-wykonania Pythona. API Pythona jest załączane w źródłowym pliku C przez załączenie pliku nagłówkowego "Python.h"
.
Kompilacja rozszerzających modułów zależy od jego zamierzonego użycia zarówno jak też od ustawień twojego systemu; szczegóły są dane w późniejszych rozdziałach.
Catatan
Інтерфейс розширення C є специфічним для CPython, а модулі розширення не працюють в інших реалізаціях Python. У багатьох випадках можна уникнути написання розширень C і зберегти переносимість на інші реалізації. Наприклад, якщо ви використовуєте виклик функцій бібліотеки C або системних викликів, вам слід розглянути можливість використання модуля ctypes
або бібліотеки cffi замість написання спеціального коду C. Ці модулі дозволяють писати код Python для взаємодії з кодом C і є більш переносимими між реалізаціями Python, ніж написання та компіляція модуля розширення C.
1.1. Contoh Sederhana¶
Давайте створимо модуль розширення під назвою spam
(улюблена їжа шанувальників Monty Python...) і, скажімо, ми хочемо створити інтерфейс Python для функції бібліотеки C system()
[1] . Ця функція приймає рядок символів із нулем у кінці як аргумент і повертає ціле число. Ми хочемо, щоб цю функцію можна було викликати з Python наступним чином:
>>> import spam
>>> status = spam.system("ls -l")
Zaczynając od stworzenia pliku spammodule.c
(Historycznie, jeśli moduł był nazwany spam
, plik C zawierający jego wypełnienie jest nazywany spammodule.c
; jeśli nazwa modułu jest bardzo długa, jak np spammify
, nazwa modułu może być po prostu spammify.c
.)
Перші два рядки нашого файлу можуть бути:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
które dociągają API Pythona (możesz dodać komentarz opisujący przeznaczenie modułu i uwagi na temat praw autorskich jeśli masz ochotę).
Catatan
Jako że Python może definiować pewne definicje preprocesora, które wpływają na pliki nagłówkowe na niektórych systemach, musisz załączyć plik Python.h
przed jakimikolwiek standardowymi nagłówkami.
#define PY_SSIZE_T_CLEAN
was used to indicate that Py_ssize_t
should be
used in some APIs instead of int
.
It is not necessary since Python 3.13, but we keep it here for backward compatibility.
See String dan penyangga, buffers for a description of this macro.
All user-visible symbols defined by Python.h
have a prefix of Py
or
PY
, except those defined in standard header files.
Tip
For backward compatibility, Python.h
includes several standard header files.
C extensions should include the standard headers that they use,
and should not rely on these implicit includes.
If using the limited C API version 3.13 or newer, the implicit includes are:
<assert.h>
<intrin.h>
(on Windows)<inttypes.h>
<limits.h>
<math.h>
<stdarg.h>
<wchar.h>
<sys/types.h>
(if present)
If Py_LIMITED_API
is not defined, or is set to version 3.12 or older,
the headers below are also included:
<ctype.h>
<unistd.h>
(on POSIX)
If Py_LIMITED_API
is not defined, or is set to version 3.10 or older,
the headers below are also included:
<errno.h>
<stdio.h>
<stdlib.h>
<string.h>
Następną rzeczą którą dodajemy do naszego pliku modułu jest zadanie C które będzie wzywane gdy wyrażenie języka pytonowskiego spam.system(string)
zostanie obliczone (zobaczymy niedługo, jak to się kończy wywołaniem):
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
const char *command;
int sts;
if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
sts = system(command);
return PyLong_FromLong(sts);
}
Istnieje prosta zamiana nazw z listy parametrów w języku pytonowskim (dla przykładu, pojedyncze wyrażenie "ls -l"
) do parametrów przekazanych do zadania C. Zadanie C zawsze ma dwa parametry, dla wygody nazywane sam - z ang. - self i args.
Parametr sam - z ang. - self - wskazuje na przedmiot modułu dla zadań na poziomie-modułu; dla sposobu postępowania wskazywałby na przykład przedmiotu.
Аргумент args буде вказівником на об’єкт кортежу Python, що містить аргументи. Кожен елемент кортежу відповідає аргументу в списку аргументів виклику. Аргументи є об’єктами Python --- щоб щось робити з ними в нашій функції C, ми повинні перетворити їх на значення C. Функція PyArg_ParseTuple()
в API Python перевіряє типи аргументів і перетворює їх на значення C. Він використовує рядок шаблону для визначення необхідних типів аргументів, а також типів змінних C, у яких зберігатимуться перетворені значення. Про це пізніше.
PyArg_ParseTuple()
повертає істину (не нуль), якщо всі аргументи мають правильний тип і його компоненти зберігаються в змінних, адреси яких передано. Він повертає false (нуль), якщо було передано недійсний список аргументів. В останньому випадку він також викликає відповідний виняток, щоб функція, що викликає, могла негайно повернути NULL
(як ми бачили в прикладі).
1.2. Intermezzo: Błędy i Wyjątki¶
Важлива конвенція в інтерпретаторі Python полягає в наступному: коли функція виходить з ладу, вона повинна встановити умову винятку та повернути значення помилки (зазвичай -1
або NULL
покажчик). Інформація про винятки зберігається в трьох членах стану потоку інтерпретатора. Це NULL
, якщо немає винятку. В іншому випадку вони є еквівалентами C членів кортежу Python, які повертає sys.exc_info()
. Це тип винятку, екземпляр винятку та об’єкт трасування. Важливо знати про них, щоб зрозуміти, як передаються помилки.
Sprzęg języka pytonowskiego określa pewien zestaw zadań do ustawiania różnych rodzajów wyjątków.
Найпоширенішим є PyErr_SetString()
. Його аргументами є об’єкт винятку та рядок C. Об’єкт винятку зазвичай є попередньо визначеним об’єктом, наприклад PyExc_ZeroDivisionError
. Рядок C вказує на причину помилки, перетворюється на об’єкт рядка Python і зберігається як "пов’язане значення" винятку.
Іншою корисною функцією є PyErr_SetFromErrno()
, яка приймає лише аргумент винятку та створює пов’язане значення шляхом перевірки глобальної змінної errno
. Найбільш загальною функцією є PyErr_SetObject()
, яка приймає два аргументи об’єкта, виняток і пов’язане з ним значення. Вам не потрібно Py_INCREF()
об’єкти, передані в будь-яку з цих функцій.
За допомогою PyErr_Occurred()
можна неруйнівним чином перевірити, чи встановлено виняток. Це повертає поточний об’єкт винятку або NULL
, якщо винятку не сталося. Зазвичай вам не потрібно викликати PyErr_Occurred()
, щоб перевірити, чи сталася помилка під час виклику функції, оскільки ви зможете визначити це за значенням, що повертається.
When a function f that calls another function g detects that the latter
fails, f should itself return an error value (usually NULL
or -1
). It
should not call one of the PyErr_*
functions --- one has already
been called by g. f's caller is then supposed to also return an error
indication to its caller, again without calling PyErr_*
, and so on
--- the most detailed cause of the error was already reported by the function
that first detected it. Once the error reaches the Python interpreter's main
loop, this aborts the currently executing Python code and tries to find an
exception handler specified by the Python programmer.
(There are situations where a module can actually give a more detailed error
message by calling another PyErr_*
function, and in such cases it is
fine to do so. As a general rule, however, this is not necessary, and can cause
information about the cause of the error to be lost: most operations can fail
for a variety of reasons.)
Щоб проігнорувати виняток, встановлений невдалим викликом функції, умову виключення потрібно явно очистити, викликавши PyErr_Clear()
. Єдиний раз, коли C-код повинен викликати PyErr_Clear()
, це якщо він не хоче передавати помилку інтерпретатору, а хоче обробити її повністю сам (можливо, спробувавши щось інше або вдаючи, що нічого не пішло не так). ).
Кожен невдалий виклик malloc()
має бути перетворений на виняток --- прямий виклик malloc()
(або realloc()
) повинен викликати PyErr_NoMemory()
і повертає сам індикатор помилки. Усі функції створення об’єктів (наприклад, PyLong_FromLong()
) уже це роблять, тому ця примітка актуальна лише для тих, хто викликає malloc()
безпосередньо.
Також зауважте, що, за винятком PyArg_ParseTuple()
та друзів, функції, які повертають цілочисельний статус, зазвичай повертають додатне значення або нуль у разі успіху та -1
у разі невдачі, як системні виклики Unix.
Нарешті, будьте обережні, щоб очистити сміття (за допомогою викликів Py_XDECREF()
або Py_DECREF()
для об’єктів, які ви вже створили), коли ви повертаєте індикатор помилки!
The choice of which exception to raise is entirely yours. There are predeclared
C objects corresponding to all built-in Python exceptions, such as
PyExc_ZeroDivisionError
, which you can use directly. Of course, you
should choose exceptions wisely --- don't use PyExc_TypeError
to mean
that a file couldn't be opened (that should probably be PyExc_OSError
).
If something's wrong with the argument list, the PyArg_ParseTuple()
function usually raises PyExc_TypeError
. If you have an argument whose
value must be in a particular range or must satisfy other conditions,
PyExc_ValueError
is appropriate.
You can also define a new exception that is unique to your module. The simplest way to do this is to declare a static global object variable at the beginning of the file:
static PyObject *SpamError = NULL;
and initialize it by calling PyErr_NewException()
in the module's
Py_mod_exec
function (spam_module_exec()
):
SpamError = PyErr_NewException("spam.error", NULL, NULL);
Since SpamError
is a global variable, it will be overwritten every time
the module is reinitialized, when the Py_mod_exec
function is called.
For now, let's avoid the issue: we will block repeated initialization by raising an
ImportError
:
static PyObject *SpamError = NULL;
static int
spam_module_exec(PyObject *m)
{
if (SpamError != NULL) {
PyErr_SetString(PyExc_ImportError,
"cannot initialize spam module more than once");
return -1;
}
SpamError = PyErr_NewException("spam.error", NULL, NULL);
if (PyModule_AddObjectRef(m, "SpamError", SpamError) < 0) {
return -1;
}
return 0;
}
static PyModuleDef_Slot spam_module_slots[] = {
{Py_mod_exec, spam_module_exec},
{0, NULL}
};
static struct PyModuleDef spam_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "spam",
.m_size = 0, // non-negative
.m_slots = spam_module_slots,
};
PyMODINIT_FUNC
PyInit_spam(void)
{
return PyModuleDef_Init(&spam_module);
}
Note that the Python name for the exception object is spam.error
. The
PyErr_NewException()
function may create a class with the base class
being Exception
(unless another class is passed in instead of NULL
),
described in Вбудовані винятки.
Note also that the SpamError
variable retains a reference to the newly
created exception class; this is intentional! Since the exception could be
removed from the module by external code, an owned reference to the class is
needed to ensure that it will not be discarded, causing SpamError
to
become a dangling pointer. Should it become a dangling pointer, C code which
raises the exception could cause a core dump or other unintended side effects.
For now, the Py_DECREF()
call to remove this reference is missing.
Even when the Python interpreter shuts down, the global SpamError
variable will not be garbage-collected. It will "leak".
We did, however, ensure that this will happen at most once per process.
We discuss the use of PyMODINIT_FUNC
as a function return type later in this
sample.
The spam.error
exception can be raised in your extension module using a
call to PyErr_SetString()
as shown below:
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
const char *command;
int sts;
if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
sts = system(command);
if (sts < 0) {
PyErr_SetString(SpamError, "System command failed");
return NULL;
}
return PyLong_FromLong(sts);
}
1.3. Kembali ke Contoh¶
Wracając do naszej przykładowego zadania, powinieneś już być w stanie zrozumieć to wyrażenie:
if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
It returns NULL
(the error indicator for functions returning object pointers)
if an error is detected in the argument list, relying on the exception set by
PyArg_ParseTuple()
. Otherwise the string value of the argument has been
copied to the local variable command
. This is a pointer assignment and
you are not supposed to modify the string to which it points (so in Standard C,
the variable command
should properly be declared as const char
*command
).
Наступний оператор — це виклик функції Unix system()
, передаючи їй рядок, який ми щойно отримали з PyArg_ParseTuple()
:
sts = system(command);
Our spam.system()
function must return the value of sts
as a
Python object. This is done using the function PyLong_FromLong()
.
return PyLong_FromLong(sts);
W tym przypadku, zwróci przedmiot liczby całkowitej (Tak, nawet liczby całkowite są przedmiotami na stercie w języku pytonowskim!)
If you have a C function that returns no useful argument (a function returning
void), the corresponding Python function must return None
. You
need this idiom to do so (which is implemented by the Py_RETURN_NONE
macro):
Py_INCREF(Py_None);
return Py_None;
Py_None
— це ім’я C для спеціального об’єкта Python None
. Це справжній об’єкт Python, а не покажчик NULL
, що означає "помилку" в більшості контекстів, як ми бачили.
1.4. Zadanie zainicjowania i tabela sposobów postępowania modułu.¶
I promised to show how spam_system()
is called from Python programs.
First, we need to list its name and address in a "method table":
static PyMethodDef spam_methods[] = {
...
{"system", spam_system, METH_VARARGS,
"Execute a shell command."},
...
{NULL, NULL, 0, NULL} /* Sentinel */
};
Зверніть увагу на третій запис (METH_VARARGS
). Це прапорець, який повідомляє інтерпретатору умову виклику, яка буде використовуватися для функції C. Зазвичай це завжди має бути METH_VARARGS
або METH_VARARGS | METH_KEYWORDS
; значення 0
означає, що використовується застарілий варіант PyArg_ParseTuple()
.
Якщо використовується лише METH_VARARGS
, функція повинна очікувати, що параметри рівня Python будуть передані як кортеж, прийнятний для аналізу через PyArg_ParseTuple()
; більше інформації про цю функцію наведено нижче.
The METH_KEYWORDS
bit may be set in the third field if keyword
arguments should be passed to the function. In this case, the C function should
accept a third PyObject *
parameter which will be a dictionary of keywords.
Use PyArg_ParseTupleAndKeywords()
to parse the arguments to such a
function.
Tabela sposobów postępowania musi być określona w strukturze definicji modułu:
static struct PyModuleDef spam_module = {
...
.m_methods = spam_methods,
...
};
This structure, in turn, must be passed to the interpreter in the module's
initialization function. The initialization function must be named
PyInit_name()
, where name is the name of the module, and should be the
only non-static
item defined in the module file:
PyMODINIT_FUNC
PyInit_spam(void)
{
return PyModuleDef_Init(&spam_module);
}
Note that PyMODINIT_FUNC
declares the function as PyObject *
return type,
declares any special linkage declarations required by the platform, and for C++
declares the function as extern "C"
.
PyInit_spam()
is called when each interpreter imports its module
spam
for the first time. (See below for comments about embedding Python.)
A pointer to the module definition must be returned via PyModuleDef_Init()
,
so that the import machinery can create the module and store it in sys.modules
.
When embedding Python, the PyInit_spam()
function is not called
automatically unless there's an entry in the PyImport_Inittab
table.
To add the module to the initialization table, use PyImport_AppendInittab()
,
optionally followed by an import of the module:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
int
main(int argc, char *argv[])
{
PyStatus status;
PyConfig config;
PyConfig_InitPythonConfig(&config);
/* Add a built-in module, before Py_Initialize */
if (PyImport_AppendInittab("spam", PyInit_spam) == -1) {
fprintf(stderr, "Error: could not extend in-built modules table\n");
exit(1);
}
/* Pass argv[0] to the Python interpreter */
status = PyConfig_SetBytesString(&config, &config.program_name, argv[0]);
if (PyStatus_Exception(status)) {
goto exception;
}
/* Initialize the Python interpreter. Required.
If this step fails, it will be a fatal error. */
status = Py_InitializeFromConfig(&config);
if (PyStatus_Exception(status)) {
goto exception;
}
PyConfig_Clear(&config);
/* Optionally import the module; alternatively,
import can be deferred until the embedded script
imports it. */
PyObject *pmodule = PyImport_ImportModule("spam");
if (!pmodule) {
PyErr_Print();
fprintf(stderr, "Error: could not import module 'spam'\n");
}
// ... use Python C API here ...
return 0;
exception:
PyConfig_Clear(&config);
Py_ExitStatusException(status);
}
Catatan
If you declare a global variable or a local static one, the module may
experience unintended side-effects on re-initialisation, for example when
removing entries from sys.modules
or importing compiled modules into
multiple interpreters within a process
(or following a fork()
without an intervening exec()
).
If module state is not yet fully isolated,
authors should consider marking the module as having no support for subinterpreters
(via Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED
).
A more substantial example module is included in the Python source distribution
as Modules/xxlimited.c
. This file may be used as a template or simply
read as an example.
1.5. Kompilacja i łączenie¶
Są jeszcze dwie rzeczy które trzeba zrobić zanim będzie można użyć nowego rozszerzenia: skompilowanie go i podłączenie z systemem Pythona. Jeśli używasz dynamicznego ładowania, szczegóły mogą zależeć od stylu dynamicznego ładowania którego twój system używa; zobacz rozdział o budowaniu rozszerzających modułów (rozdział Building C and C++ Extensions) i dodatkowe informacje które odnoszą się tylko do budowania w Windows (rozdział Membangun Ekstensi C dan C++ di Windows) po więcej informacji na ten temat.
Якщо ви не можете використовувати динамічне завантаження або якщо ви хочете зробити свій модуль постійною частиною інтерпретатора Python, вам доведеться змінити налаштування конфігурації та перебудувати інтерпретатор. На щастя, в Unix це дуже просто: просто розмістіть свій файл (наприклад, spammodule.c
) у каталозі Modules/
розпакованого вихідного дистрибутива, додайте рядок до файлу Modules/Setup.local
, що описує ваш файл:
spam spammodule.o
i przebuduj program interpretujący przez uruchomienie programu make w katalogu głównym instalacji. Możesz także uruchomić program make w podkatalogu Modules/
, ale wtedy musisz najpierw przebudować plik Makefile
tam przez uruchomienie programu make Makefile'. To jest konieczne za każdym razem gdy zmieniasz plik Setup
.)
Якщо вашому модулю потрібні додаткові бібліотеки для зв’язування, їх також можна вказати в рядку у файлі конфігурації, наприклад:
spam spammodule.o -lX11
1.6. Wywoływanie zadań języka pytonowskiego z C¶
Jak do tej pory koncentrowaliśmy się na uczynieniu zadań C możliwymi do wywołania z poziomu języka pytonowskiego. Odwrotna sytuacja jest także użyteczna: wywoływanie zadań języka pytonowskiego z poziomu języka C. To w szczególności odnosi się do bibliotek które wspierają tak zwane zadania "callback" wstecznie wywołujące. Jeśli sprzęg C używa zadań wstecznie wywołujących, odpowiednik języka pytonowskiego często potrzebuje dostarczyć mechanizm wstecznego wywołania dla programisty języka pytonowskiego; wypełnienie będzie potrzebowało wzywać zadania wywołania wstecznego z poziomu wstecznego C. Inne przypadki są także możliwe do wyobrażenia.
Szczęśliwie, program interpretujący polecenia języka pytonowskiego jest łatwo wywoływany rekursywnie i istnieje standardowy sprzęg aby wywołać zadanie języka pytonowskiego. (Nie będę rozpisywał się o tym jak wywołać czytnik języka pytonowskiego z konkretnym ciągiem znaków na wejściu --- jeśli jesteś zainteresowany, spójrz na wypełnienie opcji -c
wiersza polecenia w Modules/main.c
z kodu źródłowego języka pytonowskiego.)
Викликати функцію Python легко. По-перше, програма Python повинна якимось чином передати вам об’єкт функції Python. Ви повинні надати функцію (або інший інтерфейс), щоб це зробити. Під час виклику цієї функції збережіть вказівник на об’єкт функції Python (будьте обережні, щоб Py_INCREF()
це!) у глобальній змінній --- або будь-де, де ви вважаєте за потрібне. Наприклад, наступна функція може бути частиною визначення модуля:
static PyObject *my_callback = NULL;
static PyObject *
my_set_callback(PyObject *dummy, PyObject *args)
{
PyObject *result = NULL;
PyObject *temp;
if (PyArg_ParseTuple(args, "O:set_callback", &temp)) {
if (!PyCallable_Check(temp)) {
PyErr_SetString(PyExc_TypeError, "parameter must be callable");
return NULL;
}
Py_XINCREF(temp); /* Add a reference to new callback */
Py_XDECREF(my_callback); /* Dispose of previous callback */
my_callback = temp; /* Remember new callback */
/* Boilerplate to return "None" */
Py_INCREF(Py_None);
result = Py_None;
}
return result;
}
This function must be registered with the interpreter using the
METH_VARARGS
flag; this is described in section Zadanie zainicjowania i tabela sposobów postępowania modułu.. The
PyArg_ParseTuple()
function and its arguments are documented in section
Wydobywanie parametrów w zadaniach rozszerzających.
Макроси Py_XINCREF()
і Py_XDECREF()
збільшують/зменшують кількість посилань на об’єкт і безпечні за наявності покажчиків NULL
(але зауважте, що temp не буде бути NULL
у цьому контексті). Детальніше про них у розділі Liczby odniesień.
Пізніше, коли прийде час викликати функцію, ви викликаєте функцію C PyObject_CallObject()
. Ця функція має два аргументи, обидва вказують на довільні об’єкти Python: функція Python і список аргументів. Список аргументів завжди має бути об’єктом кортежу, довжина якого дорівнює кількості аргументів. Щоб викликати функцію Python без аргументів, передайте NULL
або пустий кортеж; щоб викликати його з одним аргументом, передайте одиночний кортеж. Py_BuildValue()
повертає кортеж, якщо його рядок формату складається з нуля або більше кодів формату в дужках. Наприклад:
int arg;
PyObject *arglist;
PyObject *result;
...
arg = 123;
...
/* Time to call the callback */
arglist = Py_BuildValue("(i)", arg);
result = PyObject_CallObject(my_callback, arglist);
Py_DECREF(arglist);
PyObject_CallObject()
повертає покажчик на об’єкт Python: це значення, яке повертає функція Python. PyObject_CallObject()
є "нейтральним щодо кількості посилань" щодо своїх аргументів. У прикладі було створено новий кортеж, який слугуватиме списком аргументів, який Py_DECREF()
-редагується відразу після виклику PyObject_CallObject()
.
Значення, що повертається PyObject_CallObject()
, є "новим": або це абсолютно новий об’єкт, або це вже існуючий об’єкт, кількість посилань на який було збільшено. Отже, якщо ви не хочете зберегти його в глобальній змінній, ви повинні якимось чином Py_DECREF()
результат, навіть (особливо!), якщо вас не цікавить його значення.
Однак перед тим, як це зробити, важливо переконатися, що значення, що повертається, не NULL
. Якщо так, функція Python припиняється, викликаючи виняток. Якщо код C, який викликав PyObject_CallObject()
, викликається з Python, тепер він має повернути вказівку про помилку своєму виклику Python, щоб інтерпретатор міг надрукувати трасування стека, або викликаючий код Python міг обробити виняткову ситуацію. Якщо це неможливо чи бажано, виняток слід очистити, викликавши PyErr_Clear()
. Наприклад:
if (result == NULL)
return NULL; /* Pass error back */
...use result...
Py_DECREF(result);
Залежно від бажаного інтерфейсу для функції зворотного виклику Python, вам також може знадобитися надати список аргументів для PyObject_CallObject()
. У деяких випадках список аргументів також надається програмою Python через той самий інтерфейс, який вказав функцію зворотного виклику. Потім його можна зберегти та використовувати так само, як і об’єкт функції. В інших випадках вам, можливо, доведеться створити новий кортеж для передачі як списку аргументів. Найпростіший спосіб зробити це — викликати Py_BuildValue()
. Наприклад, якщо ви хочете передати інтегральний код події, ви можете використати такий код:
PyObject *arglist;
...
arglist = Py_BuildValue("(l)", eventcode);
result = PyObject_CallObject(my_callback, arglist);
Py_DECREF(arglist);
if (result == NULL)
return NULL; /* Pass error back */
/* Here maybe use the result */
Py_DECREF(result);
Зверніть увагу на розміщення Py_DECREF(arglist)
одразу після виклику, перед перевіркою помилок! Також зауважте, що строго кажучи, цей код неповний: Py_BuildValue()
може не вистачати пам’яті, і це слід перевірити.
Ви також можете викликати функцію з ключовими аргументами за допомогою PyObject_Call()
, який підтримує аргументи та ключові аргументи. Як і в наведеному вище прикладі, ми використовуємо Py_BuildValue()
для створення словника.
PyObject *dict;
...
dict = Py_BuildValue("{s:i}", "name", val);
result = PyObject_Call(my_callback, NULL, dict);
Py_DECREF(dict);
if (result == NULL)
return NULL; /* Pass error back */
/* Here maybe use the result */
Py_DECREF(result);
1.7. Wydobywanie parametrów w zadaniach rozszerzających¶
Функція PyArg_ParseTuple()
оголошується таким чином:
int PyArg_ParseTuple(PyObject *arg, const char *format, ...);
Parametr arg musi być przedmiotem - krotką zawierającym listę parametrów z języka pytonowskiego dla zadania C. Parametr format musi być ciągiem formatu, którego składnia jest wyjaśniona w Mengurai argumen dan membangun nilai w podręczniku użytkownika API Python/C. Pozostałe parametry muszą być adresami zmiennych których rodzaj jest określony przez ciąg formatujący.
Зауважте, що хоча PyArg_ParseTuple()
перевіряє, чи аргументи Python мають необхідні типи, він не може перевірити дійсність адрес змінних C, переданих до виклику: якщо ви припуститеся там помилки, ваш код, ймовірно, аварійно завершить роботу. найменше перезаписувати випадкові біти в пам'яті. Тому будьте обережні!
Zauważ, że dowolne odniesienia do przedmiotów języka pytonowskiego, które są dostarczone wołającemu są pożyczonymi odniesieniami; nie zmniejszaj liczby tych odniesień.
Pewne przykładowe wywołania:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
int ok;
int i, j;
long k, l;
const char *s;
Py_ssize_t size;
ok = PyArg_ParseTuple(args, ""); /* No arguments */
/* Python call: f() */
ok = PyArg_ParseTuple(args, "s", &s); /* A string */
/* Possible Python call: f('whoops!') */
ok = PyArg_ParseTuple(args, "lls", &k, &l, &s); /* Two longs and a string */
/* Possible Python call: f(1, 2, 'three') */
ok = PyArg_ParseTuple(args, "(ii)s#", &i, &j, &s, &size);
/* A pair of ints and a string, whose size is also returned */
/* Possible Python call: f((1, 2), 'three') */
{
const char *file;
const char *mode = "r";
int bufsize = 0;
ok = PyArg_ParseTuple(args, "s|si", &file, &mode, &bufsize);
/* A string, and optionally another string and an integer */
/* Possible Python calls:
f('spam')
f('spam', 'w')
f('spam', 'wb', 100000) */
}
{
int left, top, right, bottom, h, v;
ok = PyArg_ParseTuple(args, "((ii)(ii))(ii)",
&left, &top, &right, &bottom, &h, &v);
/* A rectangle and a point */
/* Possible Python call:
f(((0, 0), (400, 300)), (10, 10)) */
}
{
Py_complex c;
ok = PyArg_ParseTuple(args, "D:myfunction", &c);
/* a complex, also providing a function name for errors */
/* Possible Python call: myfunction(1+2j) */
}
1.8. Parametry kluczowe dla zadań rozszerzających¶
Функція PyArg_ParseTupleAndKeywords()
оголошується таким чином:
int PyArg_ParseTupleAndKeywords(PyObject *arg, PyObject *kwdict,
const char *format, char * const *kwlist, ...);
Параметри arg і format ідентичні параметрам функції PyArg_ParseTuple()
. Параметр kwdict — це словник ключових слів, отриманий як третій параметр із середовища виконання Python. Параметр kwlist — це список рядків, що завершуються NULL
і ідентифікують параметри; імена зіставляються з інформацією про тип із format зліва направо. У разі успіху PyArg_ParseTupleAndKeywords()
повертає true, інакше повертає false і викликає відповідний виняток.
Catatan
Zagnieżdźone krotki nie mogą być wczytane gdy używane są parametry słów kluczowych! Parametry słów kluczowych przekazane do zadania które nie są obecne na liście kwlist spowodują że wyjątek TypeError
zostanie zgłoszony.
Tu jest przykładowy moduł który używa słów kluczowych, oparty na przykładzie Geoffa Philbricka (philbrick@hks.com):
#define PY_SSIZE_T_CLEAN
#include <Python.h>
static PyObject *
keywdarg_parrot(PyObject *self, PyObject *args, PyObject *keywds)
{
int voltage;
const char *state = "a stiff";
const char *action = "voom";
const char *type = "Norwegian Blue";
static char *kwlist[] = {"voltage", "state", "action", "type", NULL};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "i|sss", kwlist,
&voltage, &state, &action, &type))
return NULL;
printf("-- This parrot wouldn't %s if you put %i Volts through it.\n",
action, voltage);
printf("-- Lovely plumage, the %s -- It's %s!\n", type, state);
Py_RETURN_NONE;
}
static PyMethodDef keywdarg_methods[] = {
/* The cast of the function is necessary since PyCFunction values
* only take two PyObject* parameters, and keywdarg_parrot() takes
* three.
*/
{"parrot", (PyCFunction)(void(*)(void))keywdarg_parrot, METH_VARARGS | METH_KEYWORDS,
"Print a lovely skit to standard output."},
{NULL, NULL, 0, NULL} /* sentinel */
};
static struct PyModuleDef keywdarg_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "keywdarg",
.m_size = 0,
.m_methods = keywdarg_methods,
};
PyMODINIT_FUNC
PyInit_keywdarg(void)
{
return PyModuleDef_Init(&keywdarg_module);
}
1.9. Budowanie dowolnych wartości¶
Ця функція є аналогом PyArg_ParseTuple()
. Це оголошено наступним чином:
PyObject *Py_BuildValue(const char *format, ...);
Він розпізнає набір одиниць формату, подібних до тих, які розпізнає PyArg_ParseTuple()
, але аргументи (які є вхідними для функції, а не виведеними) не повинні бути вказівниками, а просто значеннями. Він повертає новий об’єкт Python, придатний для повернення з функції C, викликаної з Python.
Одна відмінність із PyArg_ParseTuple()
: у той час як останній вимагає, щоб його перший аргумент був кортежем (оскільки списки аргументів Python завжди представлені у вигляді кортежів), Py_BuildValue()
не завжди створює кортеж . Він створює кортеж, лише якщо його рядок формату містить дві або більше одиниць формату. Якщо рядок формату порожній, повертається None
; якщо він містить рівно одну одиницю формату, він повертає будь-який об’єкт, описаний цією одиницею формату. Щоб змусити його повертати кортеж розміром 0 або одиницю, візьміть рядок формату в дужки.
Приклади (ліворуч виклик, праворуч результуюче значення Python):
Py_BuildValue("") None
Py_BuildValue("i", 123) 123
Py_BuildValue("iii", 123, 456, 789) (123, 456, 789)
Py_BuildValue("s", "hello") 'hello'
Py_BuildValue("y", "hello") b'hello'
Py_BuildValue("ss", "hello", "world") ('hello', 'world')
Py_BuildValue("s#", "hello", 4) 'hell'
Py_BuildValue("y#", "hello", 4) b'hell'
Py_BuildValue("()") ()
Py_BuildValue("(i)", 123) (123,)
Py_BuildValue("(ii)", 123, 456) (123, 456)
Py_BuildValue("(i,i)", 123, 456) (123, 456)
Py_BuildValue("[i,i]", 123, 456) [123, 456]
Py_BuildValue("{s:i,s:i}",
"abc", 123, "def", 456) {'abc': 123, 'def': 456}
Py_BuildValue("((ii)(ii)) (ii)",
1, 2, 3, 4, 5, 6) (((1, 2), (3, 4)), (5, 6))
1.10. Liczby odniesień¶
У таких мовах, як C або C++, програміст відповідає за динамічний розподіл і звільнення пам’яті в купі. У C це робиться за допомогою функцій malloc()
і free()
. У C++ оператори new
і delete
використовуються, по суті, з однаковим значенням, і ми обмежимо наступне обговорення випадком C.
Every block of memory allocated with malloc()
should eventually be
returned to the pool of available memory by exactly one call to free()
.
It is important to call free()
at the right time. If a block's address
is forgotten but free()
is not called for it, the memory it occupies
cannot be reused until the program terminates. This is called a memory
leak. On the other hand, if a program calls free()
for a block and then
continues to use the block, it creates a conflict with reuse of the block
through another malloc()
call. This is called using freed memory.
It has the same bad consequences as referencing uninitialized data --- core
dumps, wrong results, mysterious crashes.
Typowymi przyczynami wycieków pamięci są nietypowe ścieżki przejścia przez kod. Dla przykładu, zadanie może zaalokować blok pamięci, wykonać pewne obliczenia, a potem uwolnić ten blok jeszcze raz. Teraz zmiana w wymaganiach dla zadania może dodać test do obliczenia który wykrywa warunek błędu i może wrócić wcześniej z zadania. Łatwo jest zapomnieć aby uwolnić zaalokowany blok pamięci podczas wybierania tej drogi wcześniejszego zakończenia, szczególnie gdy jest dodawane później do kodu. Takie przecieki, gdy raz wprowadzone, często uchodzą niewykryte przez długi czas: błędne wyjście jest wybierane tylko w małym wycinku wszystkich wywołań, i większość nowoczesnych maszyn ma mnóstwo wirtualnej pamięci, tak że wyciek staje się widoczny tylko w długo działającym procesie który używa cieknącego zadania często. Dlatego też, jest to ważne aby zapobiegać wyciekom przed ich nastąpieniem, przez powzięcie konwencji kodowania lub strategii która minimalizuje ten rodzaj błędu.
Оскільки Python активно використовує malloc()
і free()
, йому потрібна стратегія, щоб уникнути витоку пам’яті, а також використання звільненої пам’яті. Обраний метод називається reference counting. Принцип простий: кожен об’єкт містить лічильник, який збільшується, коли десь зберігається посилання на об’єкт, і зменшується, коли посилання на нього видаляється. Коли лічильник досягає нуля, останнє посилання на об’єкт було видалено, а об’єкт звільнено.
Альтернативна стратегія називається автоматична збірка сміття <automatic garbage collection>. (Іноді підрахунок посилань також називають стратегією збирання сміття, тому я використовую "автоматичний", щоб відрізнити ці два.) Великою перевагою автоматичного збирання сміття є те, що користувачеві не потрібно викликати free()
явно. (Іншою заявленою перевагою є покращення швидкості або використання пам’яті --- однак це непереконливий факт.) Недоліком є те, що для C немає справді портативного автоматичного збирача сміття, тоді як підрахунок посилань можна реалізувати портативно (за умови, що функції malloc()
і free()
доступні --- що гарантує стандарт C). Можливо, колись достатньо портативний автоматичний збирач сміття буде доступний для C. До того часу нам доведеться жити з кількістю посилань.
Podczas gdy język pytonowski używa tradycyjnego wypełnienia zliczania odniesień, on także oferuje wykrywanie cykli, które pracuje aby wykrywać cykliczne odniesienia. To pozwala aplikacjom nie martwić się o tworzenie bezpośrednich lub pośrednich cyklicznych odniesień; to są słabości wypełnienia zbiórki śmieci opartego jedynie na zliczaniu odniesień. Cykle odniesień składają się z przedmiotów które zawierają (możliwie pośrednio) odniesienia do samych siebie, tak że każdy przedmiot w cyklu ma liczbę odniesień która jest nie-zerowa. Typowe wypełnienia zliczające odniesienia nie są w stanie przejąć z powrotem pamięci należącej do któregokolwiek z przedmiotów w cyklu odniesień, ani do której odnosi się któryś z przedmiotów w cyklu, nawet jeśli nie ma więcej odniesień do cyklu samego w sobie.
The cycle detector is able to detect garbage cycles and can reclaim them.
The gc
module exposes a way to run the detector (the
collect()
function), as well as configuration
interfaces and the ability to disable the detector at runtime.
1.10.1. Zliczanie odniesień w języku pytonowskim¶
Є два макроси, Py_INCREF(x)
і Py_DECREF(x)
, які обробляють збільшення та зменшення лічильника посилань. Py_DECREF()
також звільняє об’єкт, коли кількість досягає нуля. З міркувань гнучкості він не викликає free()
напряму --- він робить виклик через вказівник на функцію в об’єкті type object. Для цієї мети (та інших) кожен об’єкт також містить покажчик на об’єкт свого типу.
Тепер залишається велике питання: коли використовувати Py_INCREF(x)
і Py_DECREF(x)
? Давайте спочатку введемо деякі терміни. Ніхто не "володіє" об'єктом; проте ви можете володіти посиланням <own a reference> на об'єкт. Кількість посилань на об’єкт тепер визначається як кількість належних посилань на нього. Власник посилання відповідає за виклик Py_DECREF()
, коли посилання більше не потрібне. Право власності на посилання можна передати. Є три способи позбутися посилання, яке належить: передати, зберегти або викликати Py_DECREF()
. Якщо забути позбутися посилання, що належить, це призводить до витоку пам’яті.
Також можна borrow [2] посилання на об’єкт. Позичальник посилання не повинен викликати Py_DECREF()
. Позичальник не повинен утримувати річ довше, ніж власник, у якого вона була позичена. Використання запозиченого посилання після того, як власник позбувся його, ризикує використати звільнену пам’ять, і його слід повністю уникати [3].
Zaletą pożyczania ponad posiadaniem odniesienia jest to że nie potrzebujesz zaprzątać swojej uwagi pozbyciem się odniesienia na wszystkich możliwych ścieżkach przejścia przez kod --- innymi słowy, z pożyczonym odniesieniem nie musisz ryzykować wycieku gdy nastąpi przedwczesne wyjście z programu. Wadą pożyczania ponad posiadaniem jest to że istnieją pewne szczególne sytuacje gdzie w wydawałoby się poprawnym kodzie pożyczone odniesienie może być użyte po tym jak właściciel od którego zostało ono pożyczone faktycznie pozbył się go.
Позичене посилання можна змінити на власне, викликавши Py_INCREF()
. Це не впливає на статус власника, у якого було запозичено посилання --- створюється нове посилання, яке належить, і надається повна відповідальність власника (новий власник повинен правильно розпоряджатися посиланням, як і попередній власник).
1.10.2. Zasady właścicielskie¶
Zawsze gdy odniesienie do przedmiotu jest przekazywane do lub z zadania, jest częścią specyfiki sprzęgu zadania to czy własność jest przekazywana z odniesieniem czy też nie.
Більшість функцій, які повертають посилання на об’єкт, передають право власності разом із посиланням. Зокрема, усі функції, функцією яких є створення нового об’єкта, такі як PyLong_FromLong()
і Py_BuildValue()
, передають право власності одержувачу. Навіть якщо об’єкт насправді не новий, ви все одно отримуєте право власності на нове посилання на цей об’єкт. Наприклад, PyLong_FromLong()
підтримує кеш популярних значень і може повертати посилання на кешований елемент.
Багато функцій, які витягують об’єкти з інших об’єктів, також передають право власності разом із посиланням, наприклад PyObject_GetAttrString()
. Однак тут картина менш зрозуміла, оскільки кілька типових процедур є винятками: PyTuple_GetItem()
, PyList_GetItem()
, PyDict_GetItem()
і PyDict_GetItemString()
повертає всі посилання, які ви запозичили з кортежу, списку або словника.
Функція PyImport_AddModule()
також повертає запозичене посилання, навіть якщо вона може фактично створити об’єкт, який повертає: це можливо, оскільки власне посилання на об’єкт зберігається в sys.modules
.
Коли ви передаєте посилання на об’єкт в іншу функцію, функція, як правило, запозичує посилання у вас --- якщо їй потрібно її зберегти, вона використовуватиме Py_INCREF()
, щоб стати незалежним власником. З цього правила є два важливих винятки: PyTuple_SetItem()
і PyList_SetItem()
. Ці функції беруть на себе право власності на переданий їм елемент --- навіть якщо вони виходять з ладу! (Зауважте, що PyDict_SetItem()
і друзі не беруть на себе право власності --- вони "нормальні".)
Коли функція C викликається з Python, вона запозичує посилання на свої аргументи від викликаючого. Виклик володіє посиланням на об’єкт, тому час життя позиченого посилання гарантується до повернення функції. Лише тоді, коли таке запозичене посилання потрібно зберегти або передати, його потрібно перетворити на власне посилання шляхом виклику Py_INCREF()
.
Odniesienie do przedmiotu zwrócone z zadania C które jest wywołane z poziomu języka pytonowskiego musi być posiadanym odniesieniem --- prawo własności jest przekazywane z zadania do wywołującego to ostatnie.
1.10.3. Cienki lód¶
Istnieje kilka sytuacji gdzie wydawałoby się nieszkodliwe użycie pożyczonych odniesień może prowadzić do kłopotów. Wszystkie one mają do czynienia z niejawnymi wezwaniami programu interpretującego polecenia języka pytonowskiego, które mogą powodować że właściciel odniesienia pozbędzie się go.
Перший і найважливіший випадок, про який варто знати, це використання Py_DECREF()
для непов’язаного об’єкта під час запозичення посилання на елемент списку. Наприклад:
void
bug(PyObject *list)
{
PyObject *item = PyList_GetItem(list, 0);
PyList_SetItem(list, 1, PyLong_FromLong(0L));
PyObject_Print(item, stdout, 0); /* BUG! */
}
To zadanie najpierw pożycza odniesienie do list[0]
, potem zamienia list[1]
na wartość 0
, i ostatecznie wypisuje pożyczone odniesienie. Wydaje się nieszkodliwe, czyż nie? A jednak jest!
Let's follow the control flow into PyList_SetItem()
. The list owns
references to all its items, so when item 1 is replaced, it has to dispose of
the original item 1. Now let's suppose the original item 1 was an instance of a
user-defined class, and let's further suppose that the class defined a
__del__()
method. If this class instance has a reference count of 1,
disposing of it will call its __del__()
method. Internally,
PyList_SetItem()
calls Py_DECREF()
on the replaced item,
which invokes replaced item's corresponding
tp_dealloc
function. During
deallocation, tp_dealloc
calls
tp_finalize
, which is mapped to the
__del__()
method for class instances (see PEP 442). This entire
sequence happens synchronously within the PyList_SetItem()
call.
Since it is written in Python, the __del__()
method can execute arbitrary
Python code. Could it perhaps do something to invalidate the reference to
item
in bug()
? You bet! Assuming that the list passed into
bug()
is accessible to the __del__()
method, it could execute a
statement to the effect of del list[0]
, and assuming this was the last
reference to that object, it would free the memory associated with it, thereby
invalidating item
.
Rozwiązanie, gdy znasz już źródło problemu, jest łatwe: tymczasowo zwiększyć ilość odniesień. Poprawna wersja zadania równa jest:
void
no_bug(PyObject *list)
{
PyObject *item = PyList_GetItem(list, 0);
Py_INCREF(item);
PyList_SetItem(list, 1, PyLong_FromLong(0L));
PyObject_Print(item, stdout, 0);
Py_DECREF(item);
}
This is a true story. An older version of Python contained variants of this bug
and someone spent a considerable amount of time in a C debugger to figure out
why his __del__()
methods would fail...
The second case of problems with a borrowed reference is a variant involving
threads. Normally, multiple threads in the Python interpreter can't get in each
other's way, because there is a global lock
protecting Python's entire object space.
However, it is possible to temporarily release this lock using the macro
Py_BEGIN_ALLOW_THREADS
, and to re-acquire it using
Py_END_ALLOW_THREADS
. This is common around blocking I/O calls, to
let other threads use the processor while waiting for the I/O to complete.
Obviously, the following function has the same problem as the previous one:
void
bug(PyObject *list)
{
PyObject *item = PyList_GetItem(list, 0);
Py_BEGIN_ALLOW_THREADS
...some blocking I/O call...
Py_END_ALLOW_THREADS
PyObject_Print(item, stdout, 0); /* BUG! */
}
1.10.4. Puste wskaźniki (NULL)¶
Загалом, функції, які приймають посилання на об’єкти як аргументи, не очікують, що ви передасте їм покажчики NULL
, і, якщо ви це зробите, створять дамп ядра (або викликатимуть дамп ядра пізніше). Функції, які повертають посилання на об’єкт, зазвичай повертають NULL
лише для того, щоб вказати, що стався виняток. Причина не перевіряти аргументи NULL
полягає в тому, що функції часто передають отримані об’єкти іншій функції --- якби кожна функція перевіряла NULL
, було б багато зайвих перевірок і код працюватиме повільніше.
Краще перевіряти NULL
лише на "джерело:", коли вказівник, який може бути NULL
отримано, наприклад, від malloc()
або від функції, яка може створити виняток.
Макроси Py_INCREF()
і Py_DECREF()
не перевіряють покажчики NULL
--- однак їх варіанти Py_XINCREF()
і Py_XDECREF()
робити.
Макроси для перевірки певного типу об’єкта (Pytype_Check()
) не перевіряють покажчики NULL
--- знову ж таки, є багато коду, який викликає кілька з них поспіль для перевірки об’єкта проти різних очікуваних типів, і це створить надлишкові тести. Варіантів із перевіркою NULL
немає.
Механізм виклику функції C гарантує, що список аргументів, переданий функціям C (args
у прикладах), ніколи не буде NULL
--- фактично він гарантує, що це завжди кортеж [4].
Дозволити вказівнику NULL
"вийти" користувачеві Python є серйозною помилкою.
1.11. Pisanie rozszerzeń w C++¶
Jest możliwe pisanie modułów rozszerzających w C++. Niektóre ograniczenia obowiązują. Jeśli główny program (program interpretujący polecenia języka pytonowskiego) jest kompilowany i łączony przez kompilator języka C, nadrzędne lub statyczne przedmioty z konstruktorami nie mogą być używane. To nie jest problemem jeśli główny program jest łączony przez kompilator C++. Zadania które będą wezwane przez program interpretujący polecenia języka pytonowskiego (w szczególności, zadania inicjujące moduł) muszą być deklarowane używając extern "C"
. Nie jest to konieczne aby zawierać plik nagłówkowy języka pytonowskiego w extern "C" {...}
--- one używają już tej formy jeśli symbol __cplusplus
jest zdefiniowany (wszystkie niedawne kompilatory C++ definiują ten symbol).
1.12. Dostarczanie sprzęgu programowania aplikacji (API) języka C dla modułu rozszerzającego¶
Wiele modułów rozszerzających po prostu dostarcza nowych zadań i typów aby były używane z języka pytonowskiego, ale czasami kod w module rozszerzającym może być użyteczny dla innych rozszerzających modułów. Na przykład, moduł rozszerzający mógłby wypełniać typ "kolekcji" który działałby jak lista bez wprowadzonego porządku. Tak jak standardowy typ listy języka pytonowskiego posiada sprzęg programowania aplikacji języka C, który pozwala modułom rozszerzającym tworzenie i zmianę list, ten nowy typ kolekcji powinien mieć zbiór zadań C dla bezpośrednich zmian z innych modułów rozszerzających.
Na pierwszy rzut oka to wydaje się proste: napisać zadania (bez deklarowania ich jako statycznych
, oczywiście), dostarczyć odpowiedni plik nagłówkowy, i udokumentować API języka C. I faktycznie to mogłoby zadziałać jeśli wszystkie rozszerzające moduły byłyby zawsze złączone statycznie z interpreterem Pythona. Gdy moduły są używane jako współdzielone biblioteki, jednakże, symbole zdefiniowane w jednym module mogą nie być widoczne dla innych modułów. Szczegóły widoczności zależą od systemu operacyjnego; niektóre systemy używają jednej nadrzędnej przestrzeni nazw dla interpretera Pythona i wszystkich modułów rozszerzających (dla Windows, na przykład), podczas gdy inne wymagają jawnej listy importowanych symboli w czasie łączenia modułów (AIX jest jednym z przykładów), lub oferują wybór różnych strategii (większość Unix-ów). I nawet jeśli symbole są widoczne nadrzędnie, moduł którego zadania ktoś chciałby uruchomić mogły nie zostać jeszcze załadowane!
Przenośność zatem wymaga aby nie czynić żadnych założeń o widoczności symboli. To oznacza, że wszystkie symbole w rozszerzających modułach powinny być deklarowane jako statyczne
, z wyjątkiem zadania zainicjowania modułu, w celu ominięcia wojen nazw z innymi modułami rozszerzającymi (jak określono w rozdziale Zadanie zainicjowania i tabela sposobów postępowania modułu.). I to oznacza, że symbole, które powinny być dostępne z innych rozszerzających modułów muszą być eksportowane w różny sposób.
Python provides a special mechanism to pass C-level information (pointers) from one extension module to another one: Capsules. A Capsule is a Python data type which stores a pointer (void*). Capsules can only be created and accessed via their C API, but they can be passed around like any other Python object. In particular, they can be assigned to a name in an extension module's namespace. Other extension modules can then import this module, retrieve the value of this name, and then retrieve the pointer from the Capsule.
Istnieje wiele sposobów w jakie kapsuły mogą być używane aby wystawiać na zewnątrz sprzęgi programowania aplikacji (API) języka C dla danego modułu rozszerzającego. Każde zadanie mogłoby dostać swoją własną kapsułę, lub wszystkie wskaźniki sprzęgu programowania aplikacji (API) języka C mogłyby być zachowane w tabeli której adres byłby opublikowany w kapsule. A różne zadania zachowania i odbioru wskaźników mogłyby być rozprowadzone na różne sposoby pomiędzy moduły dostarczające kod i moduły odbierające.
Whichever method you choose, it's important to name your Capsules properly.
The function PyCapsule_New()
takes a name parameter
(const char*); you're permitted to pass in a NULL
name, but
we strongly encourage you to specify a name. Properly named Capsules provide
a degree of runtime type-safety; there is no feasible way to tell one unnamed
Capsule from another.
W szczególności, kapsułom używanym do wystawiania sprzęgów programowania aplikacji języka C ( - z ang. - API) powinna być nadana nazwa stosująca się do następującej konwencji:
modulename.attributename
Зручна функція PyCapsule_Import()
спрощує завантаження C API, що надається через Capsule, але лише якщо назва Capsule відповідає цій умові. Така поведінка дає користувачам C API високий ступінь впевненості, що капсула, яку вони завантажують, містить правильний C API.
The following example demonstrates an approach that puts most of the burden on the writer of the exporting module, which is appropriate for commonly used library modules. It stores all C API pointers (just one in the example!) in an array of void pointers which becomes the value of a Capsule. The header file corresponding to the module provides a macro that takes care of importing the module and retrieving its C API pointers; client modules only have to call this macro before accessing the C API.
The exporting module is a modification of the spam
module from section
Contoh Sederhana. The function spam.system()
does not call
the C library function system()
directly, but a function
PySpam_System()
, which would of course do something more complicated in
reality (such as adding "spam" to every command). This function
PySpam_System()
is also exported to other extension modules.
The function PySpam_System()
is a plain C function, declared
static
like everything else:
static int
PySpam_System(const char *command)
{
return system(command);
}
The function spam_system()
is modified in a trivial way:
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
const char *command;
int sts;
if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
sts = PySpam_System(command);
return PyLong_FromLong(sts);
}
Na początku modułu, zaraz za linią
#include <Python.h>
muszą być dodane dwie linie:
#define SPAM_MODULE
#include "spammodule.h"
The #define
is used to tell the header file that it is being included in the
exporting module, not a client module. Finally, the module's mod_exec
function must take care of initializing the C API pointer array:
static int
spam_module_exec(PyObject *m)
{
static void *PySpam_API[PySpam_API_pointers];
PyObject *c_api_object;
/* Initialize the C API pointer array */
PySpam_API[PySpam_System_NUM] = (void *)PySpam_System;
/* Create a Capsule containing the API pointer array's address */
c_api_object = PyCapsule_New((void *)PySpam_API, "spam._C_API", NULL);
if (PyModule_Add(m, "_C_API", c_api_object) < 0) {
return -1;
}
return 0;
}
Note that PySpam_API
is declared static
; otherwise the pointer
array would disappear when PyInit_spam()
terminates!
Większa część pracy jest wykonywana w pliku nagłówkowym spammodule.h
, który wygląda następująco:
#ifndef Py_SPAMMODULE_H
#define Py_SPAMMODULE_H
#ifdef __cplusplus
extern "C" {
#endif
/* Header file for spammodule */
/* C API functions */
#define PySpam_System_NUM 0
#define PySpam_System_RETURN int
#define PySpam_System_PROTO (const char *command)
/* Total number of C API pointers */
#define PySpam_API_pointers 1
#ifdef SPAM_MODULE
/* This section is used when compiling spammodule.c */
static PySpam_System_RETURN PySpam_System PySpam_System_PROTO;
#else
/* This section is used in modules that use spammodule's API */
static void **PySpam_API;
#define PySpam_System \
(*(PySpam_System_RETURN (*)PySpam_System_PROTO) PySpam_API[PySpam_System_NUM])
/* Return -1 on error, 0 on success.
* PyCapsule_Import will set an exception if there's an error.
*/
static int
import_spam(void)
{
PySpam_API = (void **)PyCapsule_Import("spam._C_API", 0);
return (PySpam_API != NULL) ? 0 : -1;
}
#endif
#ifdef __cplusplus
}
#endif
#endif /* !defined(Py_SPAMMODULE_H) */
All that a client module must do in order to have access to the function
PySpam_System()
is to call the function (or rather macro)
import_spam()
in its mod_exec
function:
static int
client_module_exec(PyObject *m)
{
if (import_spam() < 0) {
return -1;
}
/* additional initialization can happen here */
return 0;
}
Główną wadą tego podejścia jest to, że plik spammodule.h
jest raczej skomplikowany. Jednakże podstawowa struktura jest taka sama dla każdego zadania które jest wystawiane na zewnątrz więc trzeba się tego uczyć tylko raz.
Ostatecznie warto wspomnieć, że kapsuły dają dodatkowe możliwości działania, które są szczególnie użyteczne dla umieszczania i zabierania miejsca w pamięci wskaźników zachowywanych w kapsule. Szczegóły są opisane w podręczniku użytkownika API Python/C w rozdziale Kapsul i w wypełnieniu programowym kapsuł (plików Include/pycapsule.h
i Objects/pycapsule.c
w dystrybucji źródłowej kodu Pythona).
Catatan kaki