python + go через c

Недавно заинтересовался объединением python и go (например для ускорения каких-либо задач). Я нашёл пару сайтов на эту тему, но выяснилось, что для их объединения нужен c. Прошу помощи: объясните человеку, который не знает c, где нужно сделать ctrl + c, ctrl + v для того, чтобы написанная на go функция вызывалась из python и отдавала результат.

P.S: сайты: 1, 2


Ответы (1 шт):

Автор решения: eri

Функции оформляешь в пакет main. Минимально выглядит так sum.go:

package main

import "C"

//export Sum
func Sum(a, b int) int {
        return a + b
}

func main() {}

//export Sum строчка обязательна, без неё python не увидит функцию через си.

Компилишь таким способом:

go build -o sum.so -buildmode=c-shared sum.go

На выходе sum.so и sum.h. В sum.h смотри типы переменных.

С типами немного надо подробнее. Функция отображается как extern GoInt Sum(GoInt a, GoInt b); GoInt определение выше typedef GoInt64 GoInt;, GoInt64 уже определенно как сишный тип long long, который в питоне преобразуется через ctypes.c_longlong в питонский int. В этом примере указание типа в питоне не обязательно, но если нужно передать что-то сложное понадобится.

В питоне загружется библиотека через ctypes:

import ctypes

lib = ctypes.cdll.LoadLibrary("./sum.so")
lib.Sum.argtypes = [ctypes.c_longlong, ctypes.c_longlong]
print(lib.Sum(1,3))

Работа со строками

Через c char

Приводим строку к C.char в Го

//export Echo
func Echo(s *C.char) *C.char {
    return C.CString(
      strings.ToUpper(
        C.GoString(
          s
        )
      )
    )
}

И в питоне:

lib.Echo.argtypes = [ctypes.c_char_p]
lib.Echo.restype = ctypes.c_char_p

print(lib.Echo('hello'.encode()).decode())

Но тут надо быть осторожным в том плане что результат выданный через указатель не освобождает память. Надо проверить освобождает ли её Питон при удалении указателя.

Через GoString

Если экспортировать функцию func Echo(s string) string {return strings.ToUpper(s) } то это может привести к проблемам со сборщиком мусора Go, а при вызове падает с ошибкой panic: runtime error: cgo result has Go pointer

Но входящие строки можно передать из питона так

class GoString(ctypes.Structure):
    "typedef struct { const char *p; ptrdiff_t n; } _GoString_;"
    def __init__(self, s):
        super().__init__(s.encode(), len(s))

    def __str__(self):
        return self.p.decode()

    _fields_ = [("p", ctypes.c_char_p),
                ("n", ctypes.c_size_t)]


lib.Echo.argtypes = [GoString]
lib.Echo(GoString('hello'))
→ Ссылка