Тонкости использования языка Python: Часть 7. Особенности взаимодействия с C++. Пакет distutils, библиотека Boost.Python, проект Cython

Статья является частью миницикла, посвященного тонким вопросам применения Python и разработки приложений на этом языке. В статье описываются ещё с три способа для интеграции С и Python-приложений: пакет distutils, библиотека Boost.Python и проект Cython.

Олег Цилюрик, преподаватель тренингового отделения, Global Logic

Фото автораОлег Иванович Цилюрик, много лет был разработчиком программного обеспечения в крупных центрах разработки: ВНИИ РТ, НПО "Дельта", КБ ПМ. Последние годы работал над проектами в области промышленной автоматики, IP телефонии и коммуникаций. Автор нескольких книг. Преподаватель тренингового отделения международной софтверной компании Global Logic.



19.12.2013

Введение

В этой статье мы рассмотрим несколько "сторонних" пакетов Python, упрощающих интеграцию С и Python фрагментов в рамках единого приложения.


Пакет distutils

Первым из рассматриваемых инструментов должен быть пакет distutils, входящий в состав стандартной библиотеки Python. Этот пакет является важным компонентом технологии Python, так как служит стандартным инструментом для распространения (дистрибуции) собственных пакетов Python. Но с его помощью можно наладить и взаимодействие между С и Python кодом, как будет показано в данной статье.

Для демонстрации distutils мы воспользуемся примером, который будет похож на модули из предыдущей статьи, правда исходный код модуля будет значительно упрощен для простоты изложения. В листинге 1 представлен исходный модуль на языке С (файл ownmod.c из архива python_c_interaction.tgz в разделе "Материалы для скачивания"):

Листинг 1. Код модуля (язык C)
#include <Python.h>

static PyObject* py_echo( PyObject* self, PyObject* args ) {
   printf( "вывод из экспортированного кода!\n" );
   return Py_None;
}

static PyMethodDef ownmod_methods[] = {
   { "echo", py_echo, METH_NOARGS, "echo function" },
   { NULL, NULL }
};

PyMODINIT_FUNC initownmod() {
   (void)Py_InitModule( "ownmod", ownmod_methods );
}

Алгоритм работы distutils определяется небольшим конфигурационным файлом setup.py (который сам пишется на Python), представленным в листинге 2:

Листинг 2. Конфигурационный файл
from distutils.core import setup, Extension

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

setup( name = 'ownmod',
       version = '1.1',
       description = 'This is a first package',
       ext_modules = [module1]
     )

Используя эти 2 файла процесс создания модуля можно запустить, как показано ниже (в архиве python_c_interaction.tgz эти действия включены в Makefile):

$ python setup.py build
running build
running build_ext
building 'ownmod' extension
creating build
creating build/temp.linux-i686-2.7
gcc -pthread -fno-strict-aliasing -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 \
    ... \
    -fPIC -I/usr/include/python2.7 -c ownmod.c -o build/temp.linux-i686-2.7/ownmod.o
creating build/lib.linux-i686-2.7
gcc -pthread -shared -Wl,-z,relro build/temp.linux-i686-2.7/ownmod.o \
    -L/usr/lib -lpython2.7 -o build/lib.linux-i686-2.7/ownmod.so
$ cp build/lib.linux-i686-2.7/ownmod.so ./

В процессе сборки создаётся подкаталог build/lib.linux-i686-2.7/, в котором и собирается файл модуля ownmod.so, а вторая команда (копирования) используется только для переноса модуля в более удобное место для тестирования.

Теперь мы можем запустить простейшее тестовое приложения, импортирующее созданный модуль ownmod:

#!/usr/bin/python
# -*- coding: utf-8 -*-
import ownmod
ownmod.echo()

Как и ожидалось, всё работает без ошибок:

$ ./test.py
вывод из экспортированного кода!

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


Библиотека Boost.Python

Проект Boost — это шаг на пути к мульти-платформенной разработке для C++ проектов, так как код, построенный на Boost, работает без изменений на платформах Windows, Linux, Solaris, FreeBSD и др.

На данный момент полный Boost представляет собой набор из нескольких десятков разнообразных инструментов от различных разработчиков, иногда очень слабо связанных между собой. Некоторые из составных частей Boost используются повсеместно (например, Thread, Regex, Boost Filesystem Library, Signals, ...). Другие составные части Boost являются, скорее, экзотикой, и используются только узкими специалистами (Lambda, Identity Type, Meta State Machine, ...). Конечно, при такой философии наполнения Boost не мог обойти стороной и Python. Эта часть Boost известна как Boost Python Library (автор Dave Abrahams).

Подготовим тестовый проект, в котором мы будем использовать Boost для вызова C++ кода. В листингах ниже представлены необходимые файлы, которые также можно найти в архиве python_c_interaction.tgz.

Листинг 3. Исходный код вызываемого модуля (файл ownmod.cc)
#include <iostream>
#include <iomanip>
using namespace std;

void echo( void ) {
   cout << "вывод из экспортированного кода!" << endl;
}
Листинг 4. Общий заголовок (файл ownmod.h)
void echo( void );
Листинг 5. Файл интерфейса Boost (файл ownmod_wrap.cc)
#include <boost/python.hpp>
#include "ownmod.h"

BOOST_PYTHON_MODULE( ownmod ) {
    using namespace boost::python;
    def( "echo", echo );
}

Соберём модуль, как показано ниже:

$ c++ -c -fpic ownmod.cc$ c++ -c -fpic ownmod_wrap.cc `python-config --includes`$ ls *.o
ownmod.o  ownmod_wrap.o
$ c++ -shared ownmod_wrap.o ownmod.o -l boost_python -o ownmod.so$ ls *.so
ownmod.so

Для тестирования используем уже знакомое приложение:

$ python test.py
вывод из экспортированного кода!

Это общая схема использования Boost.Python. На практике возможно возникновение сложностей при необходимости передавать параметры и возвращать значения. Но все особенности реализации этих действий подробно описаны в документации Boost.Python (см. раздел "Ресурсы"). В архиве python_c_interaction.tgz находится простой пример (подкаталог libra) с передачей параметров и возвратом значений (здесь не рассматривается).

Из всех рассмотренных способов взаимодействия с кодом, написанным на языке С++, Boost.Python является самым простым в использовании.

Примечание. К сожалению, Boost.Python — это уже устаревший проект, так как разработка его основных компонентов была завершена к 2003 году. Хотя он замечательно работает с Python 2, но с Python 3 всё не так просто, и вопрос адаптации Boost.Python к Python 3 требует изучения.


Проект Cython

На самом деле, проект Cython является своеобразным клоном языка Python с совместимым синтаксисом, но позволяющий, с некоторыми ограничениями на статические определения типов, компилировать программу сразу в исполняемый код (в отличие от интерпретации байт-кода в Python). В зависимости от класса задач, утверждается что это позволяет ускорить выполнение в 100 или даже 1000 раз!

Но кроме этого, Cython — язык программирования, упрощающий написание С/С++ модулей для Python-приложений. В коде, написанном на Cython, кроме стандартного синтаксиса Python поддерживается и прямой вызов функций и методов С/С++. Код Cython преобразуется в С/С++ код для последующей компиляции, и впоследствии может использоваться либо как расширение стандартного Python, либо как независимое приложение со встроенной библиотекой выполнения Cython.

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

$ yum list cython
...
Доступные пакеты
Cython.i686            0.18-1.fc17           updates

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

Листинг 6. Конфигурационный файл
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules = [ Extension( "ownmod", [ "ownmod.pyx"] ) ]

setup(
  name = 'first app',
  cmdclass = { 'build_ext': build_ext },
  ext_modules = ext_modules
)

Реализация самого модуля останется практически без изменений, как можно увидеть в листинге 7. Его исходный код также можно найти в файле ownmod.pyx в архиве python_c_interaction.tgz (Cython использует расширение .pyx, чтобы обозначить, что этот код подлежит компиляции, в отличие от расширения .py в Python):

Листинг 7. Код модуля
def echo():
   print "вывод из компилированного (cython) кода!"

Как уже говорилось, код на Cython требует компиляции. Этот процесс состоит из двух последовательных этапов:

  • Cython компилирует .pyx-файл в .c-файл, содержащий код модуля расширения Python;
  • Компилятор языка C компилирует далее полученный .c-файл в .so-файл, который можно импортировать обычным образом как модуль, прямо из Python-кода.

Обратите внимание, что благодаря обобщённой схеме (независимо от того, модуль написан на С или на Cython) с библиотекой модуля можно связать любой код на языке C. Выполняется это так:

$ python setup.py build_ext --inplace
running build_ext
cythoning ownmod.pyx to ownmod.c
building 'ownmod' extension
creating build
creating build/temp.linux-i686-2.7
gcc -pthread -fno-strict-aliasing -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 ...\
    -fPIC -I/usr/include/python2.7 -c ownmod.c -o build/temp.linux-i686-2.7/ownmod.o
gcc -pthread -shared -Wl,-z,relro build/temp.linux-i686-2.7/ownmod.o
    -L/usr/lib -lpython2.7 -o /home/olej/2013_WORK/Python/PythonC/cython/pfirst/ownmod.so
$ ls *.so
ownmod.so

Модуль можно вызывать из кода, написанного на Python, невзирая на то, что сам модуль был написан на Cython:

$ python
Python 2.7.3 (default, Jul 24 2012, 10:05:39)
[GCC 4.7.0 20120507 (Red Hat 4.7.0-5)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import ownmod
>>> ownmod.echo()
вывод из компилированного (cython) кода!
>>> quit()

В следующем примере мы реализуем математические вычисления (многократное вычисление sin(x)) и добавим в Cython-код статические определения типов в силе C (cdef), а также подключим функцию вычисления sin(x) из стандартной библиотеки C (libm.so). Два варианта реализации функции sin(x) на Cython показаны в листинге 8.:

Листинг 8. Математические вычисления на Cython (файл csin.pyx)
import time
import math

def sin1( int n ):
    cdef int i
    cdef double x, clc
    x = 0.0
    clc = time.time()
    for i in xrange( n ):
        x += math.sin( x )
    return time.time() - clc

cdef extern from "math.h":
    double sin( double )

def sin2( n ):
    x = 0.0
    clc = time.time()
    for i in xrange( n ):
        x += sin( x )
    return time.time() - clc

В листинге 9 показана эквивалентная реализация той же функции на Python.

Листинг 9. Математические вычисления на Python (файл psin.py)
import time
import math

def sin0( n ):
    x = 0.0
    clc = time.time()
    for i in xrange( n ):
        x += math.sin( x )
    return time.time() - clc

В листинге 10 показан конфигурационный файл setup.py пакета distutils для сборки Cython-реализации модуля (обратите внимание на подключение библиотеки libm.so):

Листинг 10. Конфигурационный файл
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules = [ Extension( "csin", [ "csin.pyx" ], libraries=["m"] ) ]

setup(
  cmdclass = { 'build_ext': build_ext },
  ext_modules = ext_modules
)

Теперь всё готово для сборки компилированной библиотеки csin.so для нашего модуля:

$ python setup.py build_ext --inplace
running build_ext
cythoning csin.pyx to csin.c
building 'csin' extension
creating build
creating build/temp.linux-i686-2.7
gcc -pthread -fno-strict-aliasing -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 ...\
    -fPIC -I/usr/include/python2.7 -c csin.c -o build/temp.linux-i686-2.7/csin.o
gcc -pthread -shared -Wl,-z,relro build/temp.linux-i686-2.7/csin.o -L/usr/lib ... \
    -lm -lpython2.7 -o /home/olej/2013_WORK/Python/PythonC/cython/math/csin.so

В листинге 11 показано тестовое приложение, сравнивающее 3 варианта реализации по затратам на время выполнения:

Листинг 11. Тестовое приложение (файл test.py)
#!/usr/bin/python
# -*- coding: utf-8 -*-

from sys import argv
from psin import sin0 as t1
from csin import sin1 as t2
from csin import sin2 as t3

n = ( len( argv ) > 1 and int( argv[ 1 ] ) ) or 5000000
print "время %i вычислений %5.2f секунд" % ( n, t1( n ) )
print "время %i вычислений %5.2f секунд" % ( n, t2( n ) )
print "время %i вычислений %5.2f секунд" % ( n, t3( n ) )

В итоге получается, что реализация на Python оказалась быстрее реализации на Cython, но самой быстрой оказалась Cython-версия с использованием C-функции sin(x) из библиотеки libm.so:

$ ./test.py
время 5000000 вычислений  2.40 секунд
время 5000000 вычислений  2.66 секунд
время 5000000 вычислений  0.15 секунд

Вопреки ожиданиям, компилированная Cython-версия уступила традиционной реализации на Python. Это понятно, так как большую долю времени в вычислениях занимает вычисление sin(x), производимое модулем math из библиотеки модулей Python. Но как только мы присоединили к модулю Cython C-реализацию функции sin(x) из библиотеки libm.so, то скорость вычислений выросла в 18 раз.

В этом примере было показано использование в модуле C-реализации из собранной библиотеки libm.so, без доступа к исходному коду реализации. Очевидно, что прикомпоновать к DLL модуля (подобной csin.so) объектный код, полученный компиляцией собственного исходного кода на C (или C++) ещё проще. В архиве примеров показаны несколько вариантов такого подключения (подкаталог xfirst).


Заключение

В этой статье мы познакомились ещё с тремя способами для интеграции С и Python-приложений: пакет distutils, библиотека Boost.Python и проект Cython.

В следующей статье мы рассмотрим SWIG — ещё один способ для интеграции C и Python и решим обратную задачу по вызову Python-кода из C-приложений.


Загрузка

ОписаниеИмяРазмер
интеграция Python и С/С++python_с_interaction.tgz26KB

Ресурсы

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


  • Bluemix

    Узнайте больше информации о платформе IBM Bluemix, создавайте приложения, используя готовые решения!

  • developerWorks Premium

    Эксклюзивные инструменты для построения вашего приложения. Узнать больше.

  • Библиотека документов

    Более трех тысяч статей, обзоров, руководств и других полезных материалов.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source
ArticleID=958077
ArticleTitle=Тонкости использования языка Python: Часть 7. Особенности взаимодействия с C++. Пакет distutils, библиотека Boost.Python, проект Cython
publish-date=12192013