Создание модуля расширения для Python на языке Си
Простой, а главное — рабочий пример модуля расширения на языке C для питона. Модуль называется example, а реализовывает одну функцию hello, которая вызывается с параметром who и возвращает «Hello %s» % who.
Листинг модуля example.c
Листинг setup.py
from distutils.core import setup from distutils.extension import Extension examplemodule = Extension(name=»example», sources=[‘example.c’, ]) setup(name=»example», ext_modules=[examplemodule])
python setup.py build
python setup.py install
Выполняем простенький тест:
from example import hello print hello(who=»world!»)
Полезные ссылки (на английском):
Цикл статей про Boost.Python на хабре:
UPD: Для компиляции под Python 3.x вам нужен другой example.c:
Спасибо!
Я бы еще добавил перед «Выполняем простенький тест» раздел «Инсталлируем» с одной строчкой —
[sudo] python setup.py install
И еще интересно как можно собранный у меня на машине пакет инсталлировать на компьютер клиента?
т.е. прокатит ли в скрипте инсталляции моей программы прописать строчку
python setup.py install
чтобы потом использовать данный пакет в своей программе?
Спасибо за дополнение. Обновил запись.
По поводу инсталляции — никогда не возникало такой необходимости, но вот эта команда должна создавать инсталляционный пакет (можно выбрать tgz, zip, rpm и инсталлятор для win32):
python setup.py bdist
А вот здесь можно узнать, как работать с получившимся пакетом: http://docs.python.org/install/index.html
Проверил, работает. Вместо всяког хабраклала.
Рекомендую библиотеку PyCXX для Python функций из C++ и наоборот.
>> import example
Traceback (most recent call last):
File «», line 1, in
а у меня чего-то с такой ошибкой вываливается…
Сложно сказать по такой ошибке что происходит. Вы пишете это в файле или в интерпретаторе?
У меня на FreeBSD все нормально завелось, возможно проблема на вашей стороне.
В первом листинге опечатка нужно PyMODINIT_FUNC вместо PYMODINIT_FUNC
Python — Программирование расширений на C
Любой код, который вы пишете с использованием любого скомпилированного языка, такого как C, C ++ или Java, может быть интегрирован или импортирован в другой скрипт Python. Этот код считается «расширением».
Модуль расширения Python — это не более чем обычная библиотека Си. На машинах Unix эти библиотеки обычно заканчиваются на .so (для общего объекта). На компьютерах с Windows вы обычно видите .dll (для динамически связанной библиотеки).
Предварительные условия для написания расширений
Чтобы начать писать свое расширение, вам понадобятся заголовочные файлы Python.
Пользователи Windows получают эти заголовки как часть пакета, когда они используют двоичный установщик Python.
Пользователи Windows получают эти заголовки как часть пакета, когда они используют двоичный установщик Python.
Кроме того, предполагается, что у вас есть хорошие знания C или C ++ для написания любого расширения Python с использованием C-программирования.
Сначала посмотрите на расширение Python
Для первого взгляда на модуль расширения Python вам нужно сгруппировать код в четыре части —
Функции C, которые вы хотите использовать в качестве интерфейса вашего модуля.
Таблица, отображающая имена ваших функций, когда разработчики Python видят их для функций C внутри модуля расширения.
Функции C, которые вы хотите использовать в качестве интерфейса вашего модуля.
Таблица, отображающая имена ваших функций, когда разработчики Python видят их для функций C внутри модуля расширения.
Заголовочный файл Python.h
Вам необходимо включить заголовочный файл Python.h в исходный файл C, который дает вам доступ к внутреннему API Python, используемому для подключения вашего модуля к интерпретатору.
Обязательно включите Python.h перед любыми другими заголовками, которые могут вам понадобиться. Вы должны следовать за включениями с функциями, которые вы хотите вызвать из Python.
Функции C
Сигнатуры C-реализации ваших функций всегда принимают одну из следующих трех форм:
Каждое из предыдущих объявлений возвращает объект Python. В Python нет такой вещи, как void- функция, как в C. Если вы не хотите, чтобы ваши функции возвращали значение, верните C-эквивалент значения None в Python. Заголовки Python определяют макрос Py_RETURN_NONE, который делает это для нас.
Имена ваших функций C могут быть любыми, поскольку они никогда не видны за пределами модуля расширения. Они определены как статическая функция.
Ваши функции C обычно называются путем объединения модуля Python и имен функций вместе, как показано здесь —
Таблица сопоставления методов
Эта таблица методов представляет собой простой массив структур PyMethodDef. Эта структура выглядит примерно так —
Вот описание членов этой структуры —
ml_name — это имя функции, которое представляет интерпретатор Python, когда оно используется в программах Python.
ml_meth — это должен быть адрес функции, которая имеет одну из сигнатур, описанных в предыдущем разделе.
ml_flags — сообщает интерпретатору, какая из трех сигнатур используется ml_meth.
Этот флаг обычно имеет значение METH_VARARGS.
Этот флаг может быть побитовым ИЛИ с METH_KEYWORDS, если вы хотите разрешить ключевые аргументы в вашей функции.
Это также может иметь значение METH_NOARGS, которое указывает, что вы не хотите принимать какие-либо аргументы.
ml_doc — это строка документации для функции, которая может быть NULL, если вы не хотите писать ее.
ml_name — это имя функции, которое представляет интерпретатор Python, когда оно используется в программах Python.
ml_meth — это должен быть адрес функции, которая имеет одну из сигнатур, описанных в предыдущем разделе.
ml_flags — сообщает интерпретатору, какая из трех сигнатур используется ml_meth.
Этот флаг обычно имеет значение METH_VARARGS.
Этот флаг может быть побитовым ИЛИ с METH_KEYWORDS, если вы хотите разрешить ключевые аргументы в вашей функции.
Это также может иметь значение METH_NOARGS, которое указывает, что вы не хотите принимать какие-либо аргументы.
ml_doc — это строка документации для функции, которая может быть NULL, если вы не хотите писать ее.
Эта таблица должна быть завершена с помощью стража, который состоит из значений NULL и 0 для соответствующих членов.
пример
Для описанной выше функции у нас есть следующая таблица отображения методов —
Функция инициализации
Функция инициализации должна быть экспортирована из библиотеки, которую вы будете собирать. Заголовки Python определяют PyMODINIT_FUNC, чтобы включить соответствующие заклинания для того, чтобы это произошло для конкретной среды, в которой мы компилируем. Все, что вам нужно сделать, это использовать его при определении функции.
Ваша функция инициализации C обычно имеет следующую общую структуру:
Вот описание функции Py_InitModule3 —
func — это функция для экспорта.
module _methods — это имя таблицы сопоставления, определенное выше.
docstring — это комментарий, который вы хотите оставить в своем расширении.
func — это функция для экспорта.
module _methods — это имя таблицы сопоставления, определенное выше.
docstring — это комментарий, который вы хотите оставить в своем расширении.
Соединение всего этого выглядит следующим образом —
пример
Простой пример, который использует все вышеупомянутые понятия —
Здесь функция Py_BuildValue используется для создания значения Python. Сохраните приведенный выше код в файле hello.c Мы увидим, как скомпилировать и установить этот модуль для вызова из скрипта Python.
Сборка и установка расширений
Пакет distutils позволяет очень легко распространять модули Python, как чистый Python, так и модули расширения, стандартным способом. Модули распространяются в виде исходного кода, собираются и устанавливаются с помощью сценария установки, обычно называемого setup.py, следующим образом.
Для вышеуказанного модуля вам необходимо подготовить следующий скрипт setup.py —
Теперь используйте следующую команду, которая выполнила бы все необходимые шаги компиляции и компоновки, с правильными командами и флагами компилятора и компоновщика, и скопировала полученную динамическую библиотеку в соответствующий каталог —
В системах на основе Unix вам, скорее всего, нужно будет запустить эту команду от имени пользователя root, чтобы иметь права на запись в каталог site-packages. Обычно это не проблема для Windows.
Импорт расширений
Установив расширение, вы сможете импортировать и вызывать это расширение в своем скрипте Python следующим образом:
Это даст следующий результат —
Передача параметров функции
Поскольку вы, скорее всего, захотите определить функции, которые принимают аргументы, вы можете использовать одну из других сигнатур для ваших функций Си. Например, следующая функция, которая принимает некоторое количество параметров, будет определена так:
Таблица методов, содержащая запись для новой функции, будет выглядеть так:
Вы можете использовать функцию API PyArg_ParseTuple для извлечения аргументов из одного указателя PyObject, переданного в вашу функцию C.
Компилируя новую версию вашего модуля и импортируя ее, вы можете вызывать новую функцию с любым количеством аргументов любого типа —
Вы можете, вероятно, придумать еще больше вариантов.
Функция PyArg_ParseTuple
Вот стандартная подпись для функции PyArg_ParseTuple —
Эта функция возвращает 0 для ошибок и значение, не равное 0 для успеха. кортеж — это PyObject *, который был вторым аргументом функции C. Здесь формат представляет собой строку C, которая описывает обязательные и необязательные аргументы.
Вот список кодов формата для функции PyArg_ParseTuple —
Код | Тип C | Имея в виду |
---|---|---|
с | голец | Строка Python длиной 1 становится символом C. |
d | двойной | Поплавок Python становится двойным Си. |
е | поплавок | Поплавок Python становится поплавком Си. |
я | ИНТ | Python int становится C int. |
L | долго | Int Python становится C длиной. |
L | долго долго | Python int становится C long long |
О | PyObject * | Получает ненулевую заимствованную ссылку на аргумент Python. |
s | символ * | Строка Python без встроенных нулей в C char *. |
s # | символ * + INT | Любая строка Python для адреса и длины C. |
т # | символ * + INT | Доступный только для чтения односегментный буфер для адреса C и длины. |
U | Py_UNICODE * | Python Unicode без встроенных нулей в C. |
U # | Py_UNICODE * + INT | Любой Python Unicode C адрес и длина. |
ш # | символ * + INT | Чтение / запись односегментного буфера на C адрес и длину. |
Z | символ * | Как и s, также принимает None (устанавливает C char * в NULL). |
г # | символ * + INT | Как и s #, также принимает None (устанавливает C char * в NULL). |
(…) | согласно … | Последовательность Python рассматривается как один аргумент для каждого элемента. |
| | Следующие аргументы являются необязательными. | |
: | Формат конца, а затем имя функции для сообщений об ошибках. | |
; | Конец формата с последующим полным текстом сообщения об ошибке. |
Возвращаемые значения
Вот как это будет выглядеть, если реализовано в Python —
Вы можете возвратить два значения из вашей функции следующим образом, это будет получено с помощью списка в Python.
Вот как это будет выглядеть, если реализовано в Python —
Функция Py_BuildValue
Вот стандартная подпись для функции Py_BuildValue —
Здесь формат представляет собой строку C, которая описывает объект Python для построения. Следующие аргументы Py_BuildValue являются значениями C, из которых строится результат. Результат PyObject * является новой ссылкой.
В следующей таблице перечислены наиболее часто используемые строки кода, из которых ноль или более объединяются в строковый формат.
Код | Тип C | Имея в виду |
---|---|---|
с | голец | AC char становится строкой Python длиной 1. |
d | двойной | AC double становится поплавком Python. |
е | поплавок | AC float становится Python float. |
я | ИНТ | AC int становится Python int. |
L | долго | AC долго становится Python Int. |
N | PyObject * | Пропускает объект Python и крадет ссылку. |
О | PyObject * | Проходит объект Python и INCREFs его как обычно. |
O & | конвертировать + аннулируются * | Произвольное преобразование |
s | символ * | C 0 заканчивается символом * в строке Python или NULL для None. |
s # | символ * + INT | C char * и длина до строки Python или NULL до None. |
U | Py_UNICODE * | C-широкая строка с нулевым символом в конце для Python Unicode или NULL для None. |
U # | Py_UNICODE * + INT | C-строка и длина в Python Unicode или NULL в None. |
ш # | символ * + INT | Чтение / запись односегментного буфера на C адрес и длину. |
Z | символ * | Как и s, также принимает None (устанавливает C char * в NULL). |
г # | символ * + INT | Как и s #, также принимает None (устанавливает C char * в NULL). |
(…) | согласно … | Создает кортеж Python из значений C. |
[…] | согласно … | Создает список Python из значений C. |
согласно … | Создает словарь Python из значений C, чередующихся ключей и значений. |
Код <…>создает словари из четного числа значений C, поочередно ключей и значений. Например, Py_BuildValue («
Ускоряем код на Питоне с помощью расширений на Cи
Производительность Си — в программах на Питоне.
Питон — простой, но мощный язык, который заслуженно стал одним из самых популярных. Тем не менее, иногда ему не хватает скорости статически типизированных языков с предварительной компиляцией, таких как Cи и Джава.
Почему Питон — медленный?
Как известно, код на Питоне обычно выполняется интерпретатором, а это часто очень медленный процесс — если сравнивать с Джавой и Си, в которых исходный код компилируется в машинный или байт-код (к сожалению, тема компиляции выходит за рамки статьи).
Как ускорить код на Питоне?
Обычно производительности Питона достаточно — если не приходится выполнять «тяжелые» вычисления, в случае которых как раз и могут пригодиться расширения на Cи.
Расширения — это возможность написать функцию (на Cи), скомпилировать ее в модуль Питона и использовать в исходном коде как обычную библиотеку.
Многие популярные модули написаны на Си или Cи++ (например, numpy, pandas, tensorflow и т. д.) — для повышения производительности и (или) расширения низкоуровневой функциональности.
Расширения на Си работают только на реализации Cpython, но поскольку по умолчанию используется именно она, проблемы в этом быть не должно.
Для применения этого подхода рекомендуется иметь базовые знания Си. Но если вы знаете только Питон, статья тоже будет вполне вам понятна.
Как писать расширения на Си
Прежде всего нам понадобится API для Питона, Python.h — заголовочный файл Си, который содержит всё необходимое для взаимодействия с Питоном.
На Линуксе обычно нужно установить пакет python-dev или python3-dev (если он еще не установлен). (В некоторых дистрибутивах название пакета может отличаться.)
В стандартной установке Windows Питон по умолчанию уже есть.
Прежде чем писать код расширения, необходимо включить пару основных определений и объявлений:
Эти строки рекомендуется поместить в начало файла — для обеспечения совместимости.
В Питоне всё является объектом, поэтому наша функция c_fib(n) тоже должна возвращать объект, а именно указатель PyObject (определенный в Python.h ).
После этого необходимо объявить, какие функции экспортировать из модуля, чтобы они были доступны из Питона.
Определение методов модуля
Каждый экспортированный метод представляет собой структуру, содержащую:
Имя экспортируемого метода (у нас это « c_fib »).
Фактически экспортируемый метод ( c_fib ).
И const char* с описанием метода.
Определение модуля
Функция инициализации модуля
Здесь описаны лишь несколько возможностей API для Питона — подробнее можно почитать на странице документации.
Компиляция расширения в модуль
После написания кода на Си нужно скомпилировать его в модуль для Питона — к счастью, для этого есть множество встроенных инструментов.
Создайте скрипт на Питоне (по традиции — setup.py ) со следующим кодом:
В командной строке выполните следующее:
python3 setup.py build
python3 setup.py install
В систему будут установлены только что собранные библиотеки, и ими можно будет пользоваться откуда угодно.
Для этой команды могут понадобиться права администратора или суперпользователя (root). Можно не выполнять установку для всей системы, но в этом случае для использования расширения придется задействовать относительный импорт.
Использование расширения в программе на Питоне
В файле Питона импортируйте только что созданный модуль, используя выбранное имя — в нашем случае это c_module :
Как видите, расширение используется так же, как и любой другой модуль.
Сравнение с версией на «чистом» Питоне
Теперь сравним функцию c_fib с ее аналогом на Питоне. Воспользуемся встроенным модулем time :
Как и ожидалось, функция на Си работает быстрее.
На различных компьютерах время исполнения будет различаться, но версия на Си всегда будет быстрее.
А теперь попробуем на больших числах:
Версия Си явно превосходит версию на Питоне в случае больших чисел. Если нужно выполнить несколько простых вычислений, то использование Си вряд ли будет оправданно, поскольку разница в производительности будет минимальной. Но если у вас трудоемкая операция или функция, которую необходимо выполнять много раз, скорости Питона может быть недостаточно.
И здесь расширения на Си могут здорово выручить: так вы поручите всю тяжелую работу производительному языку, а в качестве основного продолжите использовать Питон.
Примеры использования
Допустим, есть задача выполнить трудоемкие вычисления — например, для криптографического алгоритма, глубокого машинного обучения или обработки больших объемов данных. В этом случае расширения на Си могут снять нагрузку с интерпретатора Питона и ускорить работу приложения.
Что, если вам нужно создать низкоуровневый интерфейс или работать с памятью непосредственно из Питона? Здесь тоже стоит использовать расширения на Си — если вы знаете, как работать с «чистыми» указателями.
Еще один практический пример — оптимизация уже существующего «подтормаживающего» приложения на Питоне без переписывания его на другом языке.
А возможно, вы просто обожаете оптимизацию и хотите, чтобы код работал как можно быстрее, но не спешите расставаться с высокоуровневыми абстракциями для работы с сетью, графическим интерфейсом и т. д. — тогда вы определенно полюбите расширения на Си.
Время — ресурс, которого всегда не хватает. Используйте его с умом.
Заключение
Расширения на Си — отличное дополнение в арсенале разработчика, будь вы фанат производительности и эффективности или любитель смешивать различные технологии и экспериментировать с чем-то новым: вы не только получаете почти «бесплатный» скачок производительности, но и расширяете функциональные возможности Питона, не прибегая к устаревшему стеку технологий.
Благодарю за внимание.
О переводчике
Перевод статьи выполнен в Alconost.
Alconost занимается локализацией игр, приложений и сайтов на 70 языков. Переводчики-носители языка, лингвистическое тестирование, облачная платформа с API, непрерывная локализация, менеджеры проектов 24/7, любые форматы строковых ресурсов.
Мы также делаем рекламные и обучающие видеоролики — для сайтов, продающие, имиджевые, рекламные, обучающие, тизеры, эксплейнеры, трейлеры для Google Play и App Store.
1. Расширение Python с помощью C или C++¶
Очень легко добавить в Python новые встроенные модули, если вы умеете программировать на C. Такие модули расширения могут делать две вещи, которые невозможно сделать непосредственно в Python: они могут реализовывать новые встроенные типы объектов, и они могут вызывать функции библиотеки C и системные вызовы.
Для поддержки расширений Python API (программный интерфейс приложения) определяет набор функций, макросов и переменных, которые обеспечивают доступ к большинству аспектов Python системы времени выполнения. API Python включается в исходный файл C путем включения «Python.h» заголовка.
Компиляция модуля расширения зависит от его предполагаемого использования, а также от настройки системы; подробности приведены в последующих главах.
Интерфейс расширения C специфичен для CPython, и модули расширения не работают в других реализациях Python. Во многих случаях можно избежать записи расширений C и сохранить переносимость на другие реализации. Например, если используется вызов функций библиотеки C или системных вызовов, вместо записи пользовательского кода C следует использовать модуль ctypes или библиотеку cffi. Эти модули позволяют писать Python код с интерфейсом C кода и является более переносимыми между реализациями Python, чем запись и компиляция модуля расширения C.
1.1. Простой пример¶
Давайте создадим модуль расширения под названием spam (любимая еда фанатов Монти Пайтон…) и допустим, мы хотим создать Python интерфейс к функции C библиотеки system() [1]. Эта функция принимает в качестве аргумента строки символов завершаемая нулем и возвращает целое число. Мы хотим, чтобы эта функция вызывалась из Python следующим образом:
Первые две строки нашего файла могут быть:
который получает Python API (вы можете добавить комментарий, описывающий назначение модуля и уведомление об авторских правах, если хотите).
Поскольку Python могут определять некоторые предпроцессорные определения, которые влияют на стандартные заголовки в некоторых системах, вы должны включить Python.h перед включением каких-либо стандартных заголовков.
Следующее, что мы добавим в наш файл модуля, это функция C, которая будет вызвана при вычислении Python spam.system(string) выражения (мы скоро посмотрим, как она в конечном итоге будет вызвана):
Аргумент self указывает на объект модуля для функций уровня модуля; для метода он указывает на сущность объекта.
Аргумент args будет указателем на объект кортежа Python, содержащий аргументы. Каждый элемент кортежа соответствует аргументу в списке аргументов вызова. Аргументы являются Python объектами — для того, чтобы сделать что-либо с ними в нашей функции C, мы должны преобразовать их в C значения. Функция, PyArg_ParseTuple() в API Python, проверяет типы аргументов и преобразует их в C значения. Он использует строку шаблона для определения требуемых типов аргументов, а также типов переменных C, в которых будет храниться преобразованный значения. Подробнее об этом позже.
PyArg_ParseTuple() возвращает true (ненулевое значение), если все аргументы имеют правильный тип и их компоненты сохранены в переменных, адреса которых переданы. Если передан недопустимый список аргументов, возвращает false (ноль). В последнем случае также возникает соответствующее исключение, так что вызывающая функция может возвращает NULL немедленно (как мы видели в примере).
1.2. Интермеццо: ошибки и исключения¶
API Python определяет ряд функций для установки различных типов исключений.
Каждый неудачный malloc() вызов должен быть превращен в исключение, — прямой вызов malloc() (или realloc() ) должен вызвать PyErr_NoMemory() и сам вернуть индикатор отказа. Все функции создания объектов (например, PyLong_FromLong() ) уже выполняют эту функцию, поэтому эта заметка относится только к тем, кто вызывает malloc() напрямую.
Наконец, будьте внимательны к очистке мусора (совершая Py_XDECREF() или Py_DECREF() вызовы уже созданных объектов) при возвращении индикатора ошибки!
Можно также определить новое исключение, уникальное для модуля. Для этого обычно объявляется статическая переменная объекта в начале файла:
и инициализировать его в функции инициализации модуля ( PyInit_spam() ) с помощью объекта исключения:
Следует также отметить, что SpamError переменная сохраняет ссылку на вновь созданный класс исключений; это намеренно! Так как исключение может быть удалено из модуля внешними кодом, необходима собственная ссылка на класс, чтобы гарантировать, что он не будет удалён, в результате чего SpamError станет висящим указателем. Если он станет висящим указателем, C код вызывающий исключение, может вызвать дамп ядра или другие непреднамеренные побочные эффекты.
Ниже в этом примере рассматривается использование PyMODINIT_FUNC в качестве функции возвращаемого типа.
1.3. Вернемся к примеру¶
Возвращаясь к нашей функции примера, теперь вы должны уметь понимать эту инструкцию:
В этом случае он будет возвращает целочисленным объектом. (Да, даже целые числа являются объектами в куче в Python!)
1.4. Таблица методов модуля и функция инициализации¶
Я обещал показать, как spam_system() вызывается из Python программ. Сначала необходимо перечислить его имя и адрес в «таблице методов»:
Обратите внимание на третью запись ( METH_VARARGS ). Это флаг, указывающий интерпретатору соглашение о вызове, которое должно быть используемо для функции C. Обычно оно всегда должено быть METH_VARARGS или METH_VARARGS | METH_KEYWORDS ; значение 0 означает PyArg_ParseTuple() устаревшего варианта использования.
При использовании только METH_VARARGS функция должна ожидать, что параметры Python-уровня будут переданы как кортеж, приемлемые для парсинга через PyArg_ParseTuple() ; более подробная информация об этой функции приводится ниже.
На таблицу методов необходимо ссылаться в структуре определения модуля:
Удаление записей из sys.modules или импорта скомпилированных модулей в несколько интерпретаторов в рамках процесса (или после fork() без промежуточного exec() ) может создать проблемы для некоторых модулей расширения. Авторам модулей расширения следует проявлять осторожность при инициализации внутренних структур данных.
1.5. Компиляция и линковка¶
Есть еще две вещи, которые нужно сделать, прежде чем использовать новое расширение: компиляция и линковка его с Python системой. При использовании динамической загрузки подробные данные могут зависеть от стиля динамической загрузки, используемого системой; для получения дополнительной информации см. разделы о компоновке модулей расширения (глава Сборка C и C++ расширений ) и дополнительную информацию, относящуюся только к компоновке в Windows (глава Создание расширений C и C++ в Windows ).
Если динамическая загрузка невозможна или требуется сделать модуль постоянной частью Python интерпретатора, необходимо изменить настройку конфигурации и перестроить интерпретатор. К счастью, это очень просто в Unix: просто поместите свой файл ( spammodule.c например) в каталог Modules/ распакованного исходного дистрибутива, добавьте строку в файл Modules/Setup.local описывающую ваш файл:
Если модулю требуются дополнительные библиотеки для связи, они также могут быть перечислены в строке файла конфигурации для сущности:
1.6. Вызов функций Python из C¶
Пока мы сконцентрировались на том, чтобы сделать функции C вызываемыми из Python. Также полезен реверс: вызов Python функций из C. Особенно это касается библиотек, поддерживающих так называемые «колбэк» функции. Если интерфейс C использует колбэки, эквивалентный Python часто должен обеспечивать механизм колбэков программисту Python; реализация потребует вызова функций Python колбэк из колбэк C. Другие виды использования также можно себе представить.
Возвращаемое значение PyObject_CallObject() является «новым»: либо это совершенно новый объект, либо это существующий объект, число ссылок на который было увеличено. Так что, если вы не хотите сохранить его в глобальной переменной, вы должны как- то сохранить Py_DECREF() результат, даже (особенно!), если вы не заинтересованы в его значении.
Обратите внимание на размещение Py_DECREF(arglist) непосредственно после вызова, перед проверкой на ошибки! Также отметим, что строго говоря этот код не является полным: Py_BuildValue() может закончиться память, и это следует проверить.
1.7. Извлечение параметров в функциях расширения¶
Функция PyArg_ParseTuple() объявляется следующим образом:
Аргумент arg должен быть объектом кортежа, содержащим список аргументов, передаваемых из функции Python в функцию C. Аргумент format должен быть форматной строкой, синтаксис которой объясняется в Анализ аргументов и сборка значений в справочном руководстве API Python/C. Остальные аргументы должны быть адресами переменных, тип которых определяется строкой формата.
Заметим, что в то время как PyArg_ParseTuple() проверяет, что Python аргументы имеют требуемые типы, он не может проверить действительность адресов переменных C, переданных вызову: если вы совершаете там ошибки, ваша код, вероятно, потерпит крах или, по крайней мере, перезапишет случайные биты в памяти. Так что будьте осторожны!
Следует отметить, что любые Python ссылки на объекты, которые предоставляются вызывающему, являются заимствованными ссылками; не уменьшая их количество ссылок!
Некоторые примеры вызовов:
1.8. Ключевые параметры для функций расширения¶
Функция PyArg_ParseTupleAndKeywords() объявляется следующим образом:
1.9. Построение произвольных значений¶
Одно отличие с PyArg_ParseTuple() : в то время как последний требует, чтобы его первым аргументом был кортеж (так как Python списки аргументов всегда представлены как кортежи внутри), Py_BuildValue() не всегда строит кортеж. Кортеж создается только в том случае, если его строка формата содержит две или более единиц формата. Если строка формата пуста, она возвращает None ; если она содержит ровно одну единицу формата, она возвращает любой объект, описанный этой единицей формата. Чтобы принудительно вернуть кортеж размером 0 или один кортеж, введя строку формата в скобках.
Примеры (слева от вызова, справа от результирующего Python значение):
1.10. Ссылочные счетчики¶
Распространенными причинами утечек памяти являются необычные пути через код. Для сущности функции может выделить блок памяти, выполнить некоторое вычисление и затем снова освободить блок. Теперь изменение требований к функции может добавить к расчету тест, который обнаруживает условие ошибки и может преждевременно возвращает из функции. Легко забыть освободить выделенный блок памяти при взятии этого преждевременного выхода, особенно когда он добавляется позже в код. Такие утечки, когда-то введенные, часто остаются необнаруженными в течение длительного времени: выход из ошибки принимается только в небольшой доле всех вызовов, а большинство современных машин обладают большим количеством виртуальной памяти, поэтому утечка становится очевидной только в длительном процессе, который часто использует функцию утечки. Поэтому важно предотвратить утечки, имея соглашение или стратегию кодирования, которые минимизируют ошибки такого рода.
Хотя Python использует традиционную реализацию контрольного подсчета, она также предлагает детектор цикла, который работает для обнаружения опорных циклов. Это позволяет приложениям не беспокоиться о создании прямых или косвенных циклических ссылок; это слабость сбора мусора, реализуемого с использованием только отсчета ссылок. Циклы ссылок состоят из объектов, которые содержат (возможно, косвенные) ссылки на себя, так что каждый объект в цикле имеет счет ссылок, который не равен нулю. Типичные реализации подсчета ссылок не способны восстановить память, принадлежащую каким-либо объектам в опорном цикле или на которые ссылаются объекты в цикле, даже если нет дополнительных ссылок на сам цикл.
1.10.1. Ссылочный подсчет в Python¶
Преимущество заимствования перед владением ссылкой состоит в том, что вам не нужно заботиться об утилизации ссылки на всех возможных путях через код — другими словами, с заимствованной ссылкой вы не рискуете просочиться, когда выполняется преждевременный выход. Недостаток заимствования по сравнению с владением состоит в том, что существуют некоторые тонкие ситуации, когда в, казалось бы, правильный код заимствованная ссылка может быть использоваться после того, как собственник, у которого она была заимствована, фактически распорядился ею.
1.10.2. Правила владения¶
Всякий раз, когда ссылка на объект передается в функцию или из нее, она является частью спецификации интерфейса функции, передается ли право собственности с ссылкой или нет.
Ссылка на объект, возвращенный из функции C, вызываемой из Python, должна быть принадлежащей ей ссылкой, — право собственности передается из функции вызывающему объекту.
1.10.3. Тонкий лед¶
Есть несколько ситуаций, когда, казалось бы, безобидное использование заимствованной ссылки может привести к проблемам. Все это связано с неявными вызовами интерпретатора, которые могут привести к тому, что владелец ссылки распорядится ею.
Первый и самый важный случай, о котором нужно знать — использование Py_DECREF() на несвязанном объекте при заимствовании ссылки на элемент списка. Для сущность:
Решение, как только вы знаете источник проблемы, легко: временно увеличить количество ссылок. Правильная версия функции читает:
Это правдивая история. Старая версия Python содержала варианты этой ошибки, и кто-то потратил значительное количество времени в отладчике C, чтобы выяснить, почему его методы __del__() потерпят неудачу…
1.10.4. NULL указатели¶
Макросы Py_INCREF() и Py_DECREF() не проверяют NULL указатели — однако их варианты Py_XINCREF() и Py_XDECREF() делают.
Макросы для проверки определенного типа объекта ( Pytype_Check() ) не проверяют NULL указатели — есть много кода, что вызывает несколько из них в строке для проверки объекта на различные ожидаемые типы, и это приведет к избыточным тестам. Варианты с проверкой NULL отсутствуют.
Механизм вызова функции C гарантирует, что список аргументов, переданный функциям C ( args в примерах), никогда не NULL — фактически гарантирует, что он всегда является кортежем [4].
Это серьезная ошибка, когда-либо позволять NULL указателю «экранировать» к Python пользователю.
1.11. Запись расширений на языке C++¶
1.12. Предоставление C API для модуля расширения¶
Многие модули расширения просто предоставляют новые функции и типы для использовании из Python, но иногда код в модуле расширения может быть полезным для других модулей расширения. Например, модуль расширения может реализовать тип «collection», который работает как списки без заказа. Так же, как стандартный тип списка Python имеет C API, который позволяет модулям расширения создавать списки и управлять ими, этот новый тип сбора должен иметь набор функций C для прямого управления от других модулей расширения.
Существует множество способов использования капсул для экспорта C API модуля расширения. Каждая функция может получить собственную капсулу, или все указатели C API могут быть сохранены в массиве, адрес которого опубликован в капсуле. Различные задачи хранения и извлечения указателей могут быть распределены различными способами между модулем, обеспечивающим код, и клиентскими модулями.
В частности, капсулам, используемый для раскрытия C API, должно быть присвоено имя в соответствии с этим соглашением:
Удобная функция PyCapsule_Import() упрощает загрузку C API, предоставляемого через капсулу, но только если имя капсулы соответствует этому соглашению. Такое поведение дает пользователям C API высокую степень уверенности в том, что загружаемая ими капсула содержит правильный C API.
Функция PySpam_System() является простой C-функцией, объявленной static как и все остальное:
Функция spam_system() изменяется тривиальным образом: