4. Создание расширений на языках C и C++

Расширение C для CPython - это разделяемая библиотека (например, файл .so в Linux, .pyd в Windows), которая экспортирует функцию инициализации.

Чтобы быть импортируемой, общая библиотека должна быть доступна на PYTHONPATH и должна быть названа по имени модуля с соответствующим расширением. При использовании distutils правильное имя файла генерируется автоматически.

Функция инициализации имеет сигнатуру:

PyObject *PyInit_modulename(void)

Возвращает либо полностью инициализированный модуль, либо экземпляр PyModuleDef. Подробности см. в разделе Инициализация модулей C.

Для модулей с именами только ASCII функция должна быть названа PyInit_<modulename>, при этом <modulename> заменяется именем модуля. При использовании Многофазная инициализация допускаются не ASCII-имена модулей. В этом случае имя функции инициализации будет PyInitU_<modulename>, причем <modulename> кодируется с помощью кодировки Python punycode с заменой дефисов на символы подчеркивания. В Python:

def initfunc_name(name):
    try:
        suffix = b'_' + name.encode('ascii')
    except UnicodeEncodeError:
        suffix = b'U_' + name.encode('punycode').replace(b'-', b'_')
    return b'PyInit' + suffix

Можно экспортировать несколько модулей из одной общей библиотеки, определив несколько функций инициализации. Однако для их импорта необходимо использовать символические ссылки или собственный импортер, поскольку по умолчанию будет найдена только функция, соответствующая имени файла. Подробности см. в разделе «Несколько модулей в одной библиотеке « в PEP 489.

4.1. Создание расширений C и C++ с помощью distutils

Модули расширения могут быть собраны с помощью distutils, который входит в состав Python. Поскольку distutils также поддерживает создание бинарных пакетов, пользователям не обязательно иметь компилятор и distutils для установки расширения.

Пакет distutils содержит скрипт драйвера, setup.py. Это обычный Python-файл, который в самом простом случае может выглядеть следующим образом:

from distutils.core import setup, Extension

module1 = Extension('demo',
                    sources = ['demo.c'])

setup (name = 'PackageName',
       version = '1.0',
       description = 'This is a demo package',
       ext_modules = [module1])

С этим setup.py и файлом demo.c, запущенным

python setup.py build

скомпилирует demo.c и создаст модуль расширения с именем demo в каталоге build. В зависимости от системы, файл модуля окажется в подкаталоге build/lib.system и может иметь имя demo.so или demo.pyd.

В setup.py все выполнение происходит путем вызова функции setup. Она принимает переменное количество аргументов-ключей, из которых в приведенном примере используется только часть. В частности, в примере задается метаинформация для создания пакетов, а также указывается содержимое пакета. Обычно пакет содержит дополнительные модули, такие как исходные модули Python, документацию, подпакеты и т.д. Чтобы узнать больше о возможностях distutils, обратитесь к документации по distutils в Распространение модулей Python (устаревшая версия); в этом разделе описывается только сборка дополнительных модулей.

Обычно аргументы setup() предварительно вычисляются, чтобы лучше структурировать сценарий драйвера. В приведенном выше примере аргумент ext_modules для setup() представляет собой список модулей расширения, каждый из которых является экземпляром Extension. В примере экземпляр определяет расширение с именем demo, которое собирается путем компиляции единственного исходного файла demo.c.

Во многих случаях создание расширения является более сложной задачей, поскольку могут потребоваться дополнительные препроцессорные определения и библиотеки. Это показано в примере ниже.

from distutils.core import setup, Extension

module1 = Extension('demo',
                    define_macros = [('MAJOR_VERSION', '1'),
                                     ('MINOR_VERSION', '0')],
                    include_dirs = ['/usr/local/include'],
                    libraries = ['tcl83'],
                    library_dirs = ['/usr/local/lib'],
                    sources = ['demo.c'])

setup (name = 'PackageName',
       version = '1.0',
       description = 'This is a demo package',
       author = 'Martin v. Loewis',
       author_email = 'martin@v.loewis.de',
       url = 'https://docs.python.org/extending/building',
       long_description = '''
This is really just a demo package.
''',
       ext_modules = [module1])

В этом примере setup() вызывается с дополнительной метаинформацией, что рекомендуется при сборке дистрибутивных пакетов. Для самого расширения указываются определения препроцессора, каталоги include, каталоги библиотек и библиотеки. В зависимости от компилятора, distutils передает эту информацию компилятору разными способами. Например, на Unix это может привести к командам компиляции

gcc -DNDEBUG -g -O3 -Wall -Wstrict-prototypes -fPIC -DMAJOR_VERSION=1 -DMINOR_VERSION=0 -I/usr/local/include -I/usr/local/include/python2.2 -c demo.c -o build/temp.linux-i686-2.2/demo.o

gcc -shared build/temp.linux-i686-2.2/demo.o -L/usr/local/lib -ltcl83 -o build/lib.linux-i686-2.2/demo.so

Эти строки приведены только для демонстрации; пользователи distutils должны верить, что distutils правильно выполняет вызовы.

4.2. Распространение модулей расширения

Когда расширение успешно построено, есть три способа его использования.

Конечные пользователи обычно хотят установить модуль, для этого они выполняют команду

python setup.py install

Сопровождающие модулей должны создавать исходные пакеты; для этого они запускают

python setup.py sdist

В некоторых случаях в исходный дистрибутив необходимо включить дополнительные файлы; это делается с помощью файла MANIFEST.in; подробнее см. раздел Указание файлов для распространения.

Если исходный дистрибутив был успешно собран, сопровождающие также могут создавать бинарные дистрибутивы. В зависимости от платформы для этого можно использовать одну из следующих команд.

python setup.py bdist_rpm
python setup.py bdist_dumb
Вернуться на верх