A lightweight Python & Go cross-language call framework for in-process communication.
- Python Call Go - Python call Go functions, create Go type instances and invoke methods
- Go Call Python - Go call Python functions, create Python class instances and invoke methods
- Simple API - Minimal setup with intuitive interfaces
GoPyLink is designed for data exchange only. All function parameters and return values must be plain data types (primitives, collections, structs with all fields public).
Channels, goroutines, functions and interface types (except any) are not supported as parameters or return values.
Go
go get github.com/ray4go/go-ray/gopylinkPython
pip install gopylinkRequires: Go 1.21+, Python 3.7+
C build environment is required, click to show installation commands.
# Ubuntu/Debian
sudo apt install build-essential
# CentOS/RHEL
sudo yum groupinstall "Development Tools"
# macOS
xcode-select --installCreate a lib.go file:
package main
import "github.com/ray4go/gopylink"
// Functions to export - all exported methods become callable from Python
type Functions struct{}
func (Functions) Hello(name string) string {
return "Hello " + name
}
func (Functions) Add(a, b int) int {
return a + b
}
// Classes to export - each method creates a new instance
type Classes struct{}
type Counter struct {
value int
}
func (Classes) Counter(initial int) *Counter {
return &Counter{value: initial}
}
func (c *Counter) Incr(n int) int {
c.value += n
return c.value
}
func (c *Counter) Value() int {
return c.value
}
func init() {
gopylink.Init(Functions{}, Classes{})
}
func main() {}go build -buildmode=c-shared -o go.lib .# main.py
import gopylink
# Load the Go library
lib = gopylink.load_go_lib("go.lib")
# Call Go functions
result = lib.func_call("Hello", "World")
print(result) # Output: Hello World
result = lib.func_call("Add", 10, 20)
print(result) # Output: 30
# Create Go type instance and call methods
counter = lib.new_type("Counter", 0)
print(counter.Incr(5)) # Output: 5
print(counter.Incr(3)) # Output: 8
print(counter.Value()) # Output: 8Run the python script:
python main.py# main.py
import gopylink
@gopylink.export
def greet(name: str) -> str:
return f"Hi, {name}!"
@gopylink.export
def multiply(a, b):
return a * b
@gopylink.export
class Counter:
def __init__(self, initial=0):
self.value = initial
def incr(self, n):
self.value += n
return self.value
if __name__ == "__main__":
lib = gopylink.load_go_lib("go.lib")
lib.func_call("Start") # Start the Go logicpackage main
import (
"fmt"
"github.com/ray4go/gopylink"
)
type functions struct{}
func (functions) Start() {
// Call Python function
result := gopylink.PythonFuncCall("greet", "Gopher")
val, err := result.Get()
fmt.Printf("greet: %v, err: %v\n", val, err) // Output: greet: Hello, Gopher!, err: <nil>
// Call with typed result
result = gopylink.PythonFuncCall("multiply", 6, 7)
product, err := gopylink.Get[int](result)
fmt.Printf("multiply: %v, err: %v\n", product, err) // Output: multiply: 42, err: <nil>
// Create Python class instance
cnt := gopylink.NewPythonClassInstance("Counter", 10)
defer cnt.Close()
res := cnt.MethodCall("incr", 5)
val, err = res.Get()
fmt.Printf("Counter.incr: %v, err: %v\n", val, err) // Output: Counter.incr: 15, err: <nil>
}
func init() {
gopylink.Init(functions{}, nil)
}
func main() {}go build -buildmode=c-shared -o go.lib .
python main.pyNote that when calling Python from Go, the entry point remains the Python script. The Go shared library is loaded into the Python process, which then starts the Go logic.
| Function | Description |
|---|---|
load_go_lib(path) |
Load a Go shared library |
lib.func_call(name, *args) |
Call a Go function by name |
lib.new_type(name, *args) |
Create a Go type instance |
@export |
Decorator to export Python function/class to Go |
| Function | Description |
|---|---|
Init(funcRegister, classRegister) |
Initialize and register Go functions/classes |
PythonFuncCall(name, args...) |
Call a Python function |
NewPythonClassInstance(name, args...) |
Create a Python class instance |
Get[T](result) |
Get typed result from Python call |
result.Get() |
Get result as (any, error) |
handle.MethodCall(name, args...) |
Call method on Python instance |
handle.Close() |
Release Python instance |
See Go API Reference for more details.
Types are automatically converted via msgpack:
| Go Type | Python Type |
|---|---|
int, int64, etc. |
int |
float32, float64 |
float |
bool |
bool |
string |
str |
[]byte |
bytes |
| slice, array | list |
map |
dict |
struct |
dict |
nil |
None |
For detailed type conversion rules, see the Cross-Language Type Conversion Guide.
GoPyLink operations are thread-safe. Multiple goroutines/threads can safely call cross-language functions concurrently.
Go FFI has approximately 30ns overhead per call compared to native C/C++ FFI due to Go's virtual stack. This overhead is:
- Negligible for coarse-grained operations
- Significant for micro-calls requiring millions of invocations per second
Golang Parameter and return types
- Prefer concrete types (primitive types, composites, structs, and their pointers) over interface types for parameters
- Do not use interface types as return values except for
anytype.
Golang Error handling
Do not return error (as it is an interface) directly from Ray Task functon / Actor methods. Instead, return concrete
types to indicate errors (e.g., return int for error code), or just panic in your code.
When ObjectRef.GetAll() / ObjectRef.Getinto() / ray.GetN(obj) is called on a panic-ed task/actor, it will return an error.
You can use fmt.Printf("%+v", err) to print the error stack trace.
MIT