Прим. переводчика:
Как правильно подсказали в комментариях, не смотря на название, речь в данной статье пойдет не о непосредственном встраивании кода, а о создании разделяемых библиотек на Python. Но так как это всего лишь перевод, я все же решил оставить название более близкое к оригиналу.



На конференции PyGrunn 2016 я выступил с докладом о пакете Python cffi и его использовании для встраивания PyPy кода в приложения на C.

С выходом cffi 1.5.0 и его последующим включением в PyPy 5, становится возможным встраивать PyPy код. Это делается путем компиляции кода Python в динамическую библиотеку, которая затем может быть использована в любом другом языке. В этой статье я покажу вам, как это делать.

Встраиваемый API


Первый шаг заключается в определении интерфейса, который определяет, как приложению на С следует вызывать наш Python код. Мы должны указать это с помощью прототипов C-функций. Для нашего примера мы рассмотрим функцию, которая выполняет какие-то вычисления, но, конечно, это может быть все что угодно.
float compute(float first, float second);

Теперь мы должны реализовать эти вычисления на Python:
from my_library import ffi, lib

@ffi.def_extern()
def compute(first, second):
    """ Вычисляет абсолютное расстояние между двумя числами. """
    return abs(first - second)

Этот фрагмент содержит несколько особенных вещей для его правильного встраивания. Первая строка импортирует объекты ffi и lib из динамической библиотеки. Делая это реализация получает доступ к функциям, предоставляемым cffi и их можно использовать для более сложных задач, таких как выделение памяти. Имя my_library определено ниже и соответствует имени нашей динамической библиотеки.

Второе, что мы замечаем в этом фрагменте — это декоратор @ffi.def_extern. Он говорит cffi, что декорированные функции должны быть представлены в публичном API, создаваемой C-библиотеки. Декорированные функции будут сопоставлены с прототипами, указанными в объявлении API и их аргументами и возвращаемые значения будут преобразовываться автоматически.

Скрипт генерирующий библиотеку


Теперь, когда у нас есть API и его реализация, мы должны на самом деле куда-то его встроить. Для этого мы используем скрипт, который генерирует динамическую библиотеку. Он требует, чтобы два фрагмента кода, приведенные выше, находились в файлах api.h и implementation.py.
import cffi
ffi = cffi.FFI()

ffi.embedding_api(open("api.h").read())
ffi.embedding_init_code(open("implementation.py").read())

ffi.set_source("my_library", "")
ffi.compile(verbose=True)

Этот скрипт очень прост. Мы должны указать наш API и предоставить его реализацию. Они оба читаются с диска и соответствуют фрагментам кода, приведенным в предыдущем разделе.

После указания исходников, мы должны сказать cffi название нашей библиотеки. В этом примере это my_library. Кроме того, здесь есть место для добавления дополнительного C-кода, предоставляющего типы для заголовочного файла нашего API, например, с помощью подключения соответствующих заголовочных файлов (что не допускается в embedding_api). Остается только скомпилировать исходники, чтобы создать файл нашей библиотеки.

Запуск скрипта выводит некоторую информацию и создает нашу библиотеку:
$ pypy embed.py
generating ./my_library.c
running build_ext
building 'my_library' extension
...

$ ls my_library.dylib
-rwxr-xr-x  1 djinn  staff  9856 May 15 14:46 my_library.dylib

Все что осталось — это где-нибудь ее использовать!

Использующее приложение


Использовать встраиваемый Python-код на самом деле очень просто. Это можно сделать с помощью следующего кода:
#include <stdio.h>
#include "api.h"

int main(void) {
    float result = compute(12.34f, 10.0f);
    printf("The result: %f\n", result);

    return 0;
}

Как вы видите, для вызова нашего Python-кода практически ничего не нужно. Используя API CPython, вы должны были бы запустить интерпретатор и выполнить множество преобразований параметров и возвращаемого значения. Но не с cffi! Созданная библиотека берет все на себя, так что вы можете сосредоточиться на действительно полезной работе. Последнее, что я должен показать вам, это как скомпилировать и запустить этот код.
$ clang -o test test.c my_library.dylib

$ ./test
The result: 2.340000

И вот оно! С помощью всего нескольких строк кода мы написали программу на C, которая запускает интерпретатор PyPy и выполняет наш Python-код, как если бы это был код на C. Конечно, я показал вам только основы, но это действительно мощная технология. Для получения дополнительной информации, можно посмотреть в документацию cffi.
Поделиться с друзьями
-->

Комментарии (12)


  1. kloppspb
    05.06.2016 13:36
    +7

    То есть фактически ни о каком «встраивании кода» (как можно предположить из заголовка) речи и не идёт…


    1. iroln
      05.06.2016 15:03

      А что тогда понимается под фактическим встраиванием? Чем этот подход по своей сути отличается от того, что предлагает стандартный механизм или Boost::Python? Питоновский код всё так же исполняется в интерпретаторе, просто вместо вот этого:

      Используя API CPython, вы должны были бы запустить интерпретатор и выполнить множество преобразований параметров и возвращаемого значения.
      используется обёртка в виде динамической библиотеки, в которой всё это под капотом.

      Думаю, отлаживать такой питоновский код становится не очень удобно из-за всей этой обвязки с импортом модуля расширения. В доке даже есть раздел об этом.

      Хотя идея мне, вообще, нравится. Всё это, например, можно было бы использовать в приложении с плагинами.


      1. kloppspb
        05.06.2016 15:40
        +2

        >используется обёртка в виде динамической библиотеки, в которой всё это под капотом.

        Вот и я об этом. Просто линковка библиотек на другом языке и интерфейс для их использования. А встраивание кода — это использование кода в оригинальном исходнике, например, как в C::Blocks (Perl).


        1. ZyXI
          06.06.2016 01:39

          Если очень нужно, то допилить встраивание именно до такого варианта с помощью C’шного препроцессора и простого скрипта на том же Python, который будет вырезать код на Python во временный файл и запускать создание библиотеки, несложно. А вот сделать код, который создаёт такие библиотеки сложнее.


          Хотя пока такой скрипт не приведён название всё же стоит изменить на «создание разделяемых библиотек на Python».


          1. DuDDiTs
            06.06.2016 08:30

            У оригинальной статьи в названии все же «Встраивание...», хотя вы правы, в статье речь на самом деле идет только о создании библиотек.


          1. kloppspb
            06.06.2016 11:17

            Не нужно. И так просто, «с помощью препроцессора и простого скрипта» ничего не получится. Потому что суть встраивания не в выдирании модулей из общего кода, а в совместной компиляции, при которой гостевой код имеет полный доступ ко всей инфраструктуре «хост»-кода без дополнительных усилий. Ещё наглядный пример — inline assembler в C/C++, причём больше MASM/Intel-вариант, чем GNU.

            Очевидно, что встраивать в первую очередь имеет смысл низкоуровневый язык в язык более высокого уровня. А вот осмысленность обратной ситуации для меня под большим вопросом.


  1. mbait
    05.06.2016 18:02
    +2

    Есть примеры, где это может понадобиться? Обычно нужно обратное — пишется ядро на C или C++ и используется в Python-приложении.


    1. NeonMercury
      05.06.2016 18:21
      +1

      На вскидку, скриптование в игре. Да и вообще везде, где надо программировать логику без перекомпиляции.


    1. iroln
      05.06.2016 19:16
      +3

      Мы использовали Python-плагины в некотором околонаучном софте, который был написан на C++. Система плагинов была разработана так, чтобы можно было писать плагины и на C++ и на Python. Это очень удобно для проверки и обкатки каких-то новых алгоритмов/концепций/методов, связанных с предметной областью. Сначала пишется прототип на Python, подключается в виде плагина, алгоритм отлаживается, дорабатывается. Когда всё готово, делается реализация на C++, и плагин на Python заменяется плагином на C++ с тем же интерфейсом.


  1. 007913
    06.06.2016 06:07

    Какие ограничения по использованию питоновских модулей с помощью такого импорта?


  1. Bombus
    06.06.2016 13:56

    Прошу уточнить вопросы технического плана:
    1) gcc вместо Clang отаботает также, с теми же параметрами? Ни разу с Clang работать не приходилось.
    2) Указано что *.dylib — динамическая библиотека, но в примере указан вариант с включением файла в конечный исполняемый файл. Поправьте, если это не так. Вопрос — это на самом деле динамическая библиотека, то что в Linux часто имеет шаблон libNamelibrary.so?


    1. DuDDiTs
      06.06.2016 14:24

      Точно ответить, к сожалению, сейчас не могу.

      С gcc должно работать также. Во всяком случае, для такого простого примера.
      По второму вопросу — видимо да, на Linux будет генерироваться библиотека с расширением .so
      *.dylib — расширение для динамических библиотек используемое в OS X