Интеграция функций Python в Golang

Недавно я работал над сервисом на Python, которому требовалась интеграция с приложением на Go. Звучит довольно просто. Момент, который привлек мое внимание, был следующим:

Python может быть динамически подключен как разделяемая библиотека (.dll / .lib.so).

Что же представляют собой эти разделяемые библиотеки? Они содержат скомпилированный код и данные, которые могут быть динамически связаны с другими программами во время выполнения.

https://t.me/Golang_google – Golang в телеграм! Наш обучающий на практике канал .

И почему это представляет для нас интерес на данном этапе? Говоря простым языком, это может помочь собрать сервис python в модуль, который можно импортировать в Go и использовать (так же просто, как import xyz).

Давайте рассмотрим шаг за шагом, как мы можем это сделать. Предварительные условия, которые нам потребуются, это установка Python3 и python3-devel. Также нам потребуется инструмент pkg-config.

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

 1 import sys
 2
 3 def print_v():
 4  print("Python version {}".format(sys.version))

Мы можем сохранить приведенный выше скрипт как print_version.py в приведенной ниже структуре каталогов:

└── py-app
    └── print_version.py

На стороне Go мы можем создать простое приложение и сохранить его в следующей структуре:

└── py-app
│   └── print_version.py
│   
└── go-app
    ├── go.mod
    └── main.go

Следует помнить, что main.go интегрирует Python в Go, поэтому давайте разделим его на части.

 1 package main
 2 
 3 // #cgo pkg-config: python3-embed
 4 // #include <Python.h>
 5 import "C"
 6 import (
 7     "fmt"
 8     "os"
 9 )

Строки 3 и 4 указывают на то, что мы будем использовать встроенную версию python, исполняемую с помощью C (строка 5).

10 func main() {
11    
12     C.Py_Initialize()

Любой код Python должен быть выполнен с помощью интерпретатора Python, поэтому он инициализируется в строке 12.

Далее начинается сложная часть. Поскольку мы создаем наше Python-приложение независимо от Go, мы собираемся использовать py-app как python-модуль в нашем main.go . Но py-app – это приложение, определяемое пользователем. Как же python распознает его как модуль? Это можно сделать одним из следующих двух способов в зависимости от конкретного случая использования.

  1. Добавьте py-app в папку lib установки Python, где находятся все модули.
  2. Установите каталог проекта в путь поиска модулей Python.

Давайте выберем подход 2.

13 // Get the current Python module search path
14 sysPath := C.PySys_GetObject(C.CString("path"))
15 if sysPath == nil {
16   fmt.Println("Error getting sys.path")
17   return
18 }
19 
20 // Convert sys.path to a Go slice of strings
21 var pathSlice []string
22 numPaths := C.PyList_Size(sysPath)
23 for i := C.Py_ssize_t(0); i < numPaths; i++ {
24   pathItem := C.PyList_GetItem(sysPath, i)
25   pathStr := C.PyUnicode_AsUTF8(pathItem)
26   pathSlice = append(pathSlice, C.GoString(pathStr))
27 }

Строка 14 эквивалентна вызову sys.path в Python. Это возвращает список путей, которые преобразуются в фрагменты строк Go, с которыми можно работать.

28 // Get current working directory
29 cwd, err := os.Getwd()
30 if err != nil {
31   fmt.Println("Error getting current working directory:", err)
32   return
33 }
34 
35 pathSlice = append(pathSlice, cwd)

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

36 // Convert the modified path slice back to a Python list
37 modifiedPath := C.PyList_New(C.Py_ssize_t(len(pathSlice)))
38 for i, path := range pathSlice {
39   pathStr := C.CString(path)
40   pathItem := C.PyUnicode_FromString(pathStr)
41   C.PyList_SetItem(modifiedPath, C.Py_ssize_t(i), pathItem)
42 }
43 
44 // Set the modified sys.path back to Python
45 C.PySys_SetObject(C.CString("path"), modifiedPath)

Поэтому мы преобразуем фрагмент обратно в список Python и зададим его в качестве пути поиска модулей по умолчанию в Python.

Вот он, крутой прием, которого мы так долго ждали. Давайте используем наше приложение Python, которое называется print_version.py, как модуль в Go.

46 // Import and use the Python module
47 moduleName := C.CString("print_version")
48 module := C.PyImport_ImportModule(moduleName)
49  
50 // Check if the module was imported successfully
51 if module == nil {
52   fmt.Println("Error importing Python module")
53   return
54 }

И как любой модуль, давайте обратимся к нему – это функция print_v().

55 // Access Function Definition
56 functionName := C.CString("print_v")
57 function := C.PyObject_GetAttrString(module, functionName)
58 
59 if function == nil {
60   panic("Error importing function")
61 }

Это извлекает определение нашей функции, и все, что остается, это вызвать ее.

62 args := C.PyTuple_New(0)
63 
64 C.PyObject_CallObject(function, args)

Теперь, когда мы закончили использовать интерпретатор Python, мы можем очистить его ресурсы.

65  C.Py_Finalize()
66 }

Давайте подытожим, на чем мы остановились. У нас есть исходный код py-app и есть код Go для использования Python-приложения в качестве модуля.

Чтобы связать все вместе, мы собираемся создать Python-приложение в виде модуля с помощью Cython.

Cython – это язык программирования, который является надмножеством Python. Он предназначен для написания кода на Python, который может быть скомпилирован в C или C++ для повышения производительности.

Мы собираемся использовать эту библиотеку Cython для преобразования нашего приложения Python в набор кодов C, а затем собрать его в файлы разделяемых объектов (.so). Чтобы использовать Cython, выполните команду pip3 install Cython .

Мы можем создать файл setup, который соберет наш исходный код Python в папке py-app.

└── py-app
│   ├── print_version.py
│   └── setup.py
│   
└── go-app
    ├── go.mod
    └── main.go
1 from setuptools import setup, Extension
2 from Cython.Build import cythonize
3 
4 extensions = [
5    Extension("print_version", ["print_version.py"]),
6 ]
7 
8 setup(
9    name='py-app',
10   ext_modules=cythonize(extensions),
11 )

Мы используем Cython для преобразования нашего кода python в файлы C, которые затем собираются в библиотеки общих объектов с помощью setuptools.

Сценарий выполняется с помощью следующей команды

python3 setup.py build_ext --inplace 

Эта команда создаст модуль расширения Cython и поместит скомпилированные файлы (.so) в текущий каталог.

Интеграция функций Python в Golang

Это создает исходный текст на языке C для нашего файла Python и файл разделяемых объектов (.so). Мы можем скопировать этот файл в наше приложение Go, как показано ниже:

Интеграция функций Python в Golang

Теперь давайте выполним сценарий Go с помощью go run main.go, и вуаля – модуль запущен.

Интеграция функций Python в Golang
+1
0
+1
2
+1
0
+1
0
+1
0

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *