Сделать исполняемый файл из нескольких файлов PyX, используя Cython - PullRequest
0 голосов
/ 24 октября 2018

Я пытаюсь сделать один исполняемый файл Unix из моих исходных файлов Python.

У меня есть два файла, p1.py и p2.py

p1.py: -

from p2 import test_func 
print (test_func())

p2.py: -

def test_func():
    return ('Test')

Теперь, как мы видим, p1.py зависит от p2.py.Я хочу сделать исполняемый файл, объединив два файла вместе.Я использую Cython.

Я изменил имена файлов на p1.pyx и p2.pyx соответственно.

Теперь я могу сделать исполняемый файл с помощью Cython,

cython p1.pyx --embed

Это сгенерируетисходный файл C называется p1.c.Далее мы можем использовать gcc, чтобы сделать его исполняемым,

gcc -Os -I /usr/include/python3.5m -o test p1.c -lpython3.5m -lpthread -lm -lutil -ldl 

Но как объединить два файла в один исполняемый файл?

1 Ответ

0 голосов
/ 24 октября 2018

Есть несколько циклов, через которые вы должны перейти, чтобы заставить его работать.

Во-первых, вы должны знать, что полученный исполняемый файл представляет собой очень тонкий слой, который просто делегирует всю работу (т.е. вызывает функции из) pythonX.Ym.so.Вы можете увидеть эту зависимость при вызове

ldd test
...
libpythonX.Ym.so.1.0 => not found
...

Итак, чтобы запустить программу, вам нужно либо иметь LD_LIBRARY_PATH, показывающий местоположение libpythonX.Ym.so, либо собрать exe с опцией --rpath,в противном случае при запуске test динамический загрузчик выдаст ошибку, подобную

/ test: ошибка при загрузке общих библиотек: libpythonX.Ym.so.1.0: невозможно открыть файл общих объектов:Нет такого файла или каталога

Общая команда сборки будет выглядеть следующим образом:

gcc -fPIC <other flags> -o test p1.c -I<path_python_include> -L<path_python_lib> -Wl,-rpath=<path_python_lib> -lpython3.6m <other_needed_libs>

Полученный исполняемый файл test ведет себя точно так же, как если бы он был интерпретатором Python,Это означает, что теперь test потерпит неудачу, потому что он не найдет модуль p2.

Одно простое решение, где для цитонизации p2-модуля на месте (cythonize p2.pyx -i) вы получите желаемое поведение -тем не менее, вам придется распространять полученный совместно используемый объект p2.so вместе с test.

. Легко объединить оба расширения в один исполняемый файл - просто передайте оба c-файла cthon в gcc:

# creates p1.c:
cython --empbed p1.pyx
# creates p2.c:  
cython p2.pyx
gcc ... -o test p1.c p2.c ...

Но теперь возникает новая (или старая) проблема: результирующий test -исполняемый файл не может снова найти модуль p2, потому что нет p2.py и p2.so наpython-path.

Существует два похожих SO вопроса по этой проблеме: здесь и здесь .В вашем случае предлагаемые решения являются своего рода излишними, здесь достаточно инициализировать модуль p2, прежде чем он будет импортирован в p1.pyx -файл, чтобы заставить его работать:

# making init-function from other modules accessible:
cdef extern  object PyInit_p2();

#init/load p2-module manually
PyInit_p2()  #Cython handles error, i.e. if NULL returned

# actually using already cached imported module
#          no search in python path needed
from p2 import test_func
print(test_func())

Вызов функции initмодуля до его импорта (на самом деле модуль не будет действительно импортирован второй раз, только поиск в кеше) работает также, если существуют циклические зависимости между модулями.Например, если модуль p2 импортирует модуль p3, который в свою очередь импортирует p2.

Начиная с Cython 0.29, Cython использует многофазную инициализацию по умолчанию для Python> = 3.5, вызывая, таким образом, PyInit_p2 недостаточно (см., например, этот SO-post ).Чтобы отключить эту многофазную инициализацию, -DCYTHON_PEP489_MULTI_PHASE_INIT=0 следует передать в gcc или аналогично другим компиляторам.


Предупреждение: если мы объявим PyInit_p2() как

from cpython cimport PyObject
cdef extern  PyObject *PyInit_p2();

PyInit_p2(); # TODO: error handling if NULL is returned

Cython больше не будет обрабатывать ошибки и несет нашу ответственность.Вместо

PyObject *__pyx_t_1 = NULL;
__pyx_t_1 = PyInit_p2(); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 4, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_1);
__Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;

, созданного для object -версии, сгенерированный код становится просто:

(void)(PyInit_p2());

т.е. без проверки ошибок!

С другой стороны, используя

cdef extern from *:
    """
    PyObject *PyInit_p2(void);
    """
    object PyInit_p2()

не будет работать с g ++ - нужно добавить extern C к объявлению.

...