Интеграция функций 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 распознает его как модуль? Это можно сделать одним из следующих двух способов в зависимости от конкретного случая использования.
- Добавьте py-app в папку lib установки Python, где находятся все модули.
- Установите каталог проекта в путь поиска модулей 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) в текущий каталог.
Это создает исходный текст на языке C для нашего файла Python и файл разделяемых объектов (.so). Мы можем скопировать этот файл в наше приложение Go, как показано ниже:
Теперь давайте выполним сценарий Go с помощью go run main.go, и вуаля – модуль запущен.