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 PythontypeFunctionsstruct{}
func (Functions) Hello(namestring) string {
return"Hello "+name
}
func (Functions) Add(a, bint) int {
returna+b
}
// Classes to export - each method creates a new instancetypeClassesstruct{}
typeCounterstruct {
valueint
}
func (Classes) Counter(initialint) *Counter {
return&Counter{value: initial}
}
func (c*Counter) Incr(nint) int {
c.value+=nreturnc.value
}
func (c*Counter) Value() int {
returnc.value
}
funcinit() {
gopylink.Init(Functions{}, Classes{})
}
funcmain() {}go build -buildmode=c-shared -o go.lib .# main.pyimportgopylink# Load the Go librarylib=gopylink.load_go_lib("go.lib")
# Call Go functionsresult=lib.func_call("Hello", "World")
print(result) # Output: Hello Worldresult=lib.func_call("Add", 10, 20)
print(result) # Output: 30# Create Go type instance and call methodscounter=lib.new_type("Counter", 0)
print(counter.Incr(5)) # Output: 5print(counter.Incr(3)) # Output: 8print(counter.Value()) # Output: 8Run the python script:
python main.py# main.pyimportgopylink@gopylink.exportdefgreet(name: str) ->str:
returnf"Hi, {name}!"@gopylink.exportdefmultiply(a, b):
returna*b@gopylink.exportclassCounter:
def__init__(self, initial=0):
self.value=initialdefincr(self, n):
self.value+=nreturnself.valueif__name__=="__main__":
lib=gopylink.load_go_lib("go.lib")
lib.func_call("Start") # Start the Go logicpackage main
import (
"fmt""github.com/ray4go/gopylink"
)
typefunctionsstruct{}
func (functions) Start() {
// Call Python functionresult:=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 resultresult=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 instancecnt:=gopylink.NewPythonClassInstance("Counter", 10)
defercnt.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>
}
funcinit() {
gopylink.Init(functions{}, nil)
}
funcmain() {}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