GitHub - ray4go/GoPyLink: Call Go from Python and vice versa, simply
A lightweight Python & Go cross-language call framework for in-process communication.
Features
- 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
Limitations
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.
Installation
Go
go get github.com/ray4go/go-ray/gopylink
Python
Requires: 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 --install
Python calling Go
1. Export Go Functions/Types
Create 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() {}
2. Build Go Shared Library
go build -buildmode=c-shared -o go.lib .3. Call Go from Python
# 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: 8
Run the python script:
Go calling Python
1. Export Python Functions/Classes
# 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 logic
2. Call Python from Go
package 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() {}
3. Build and Run
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.
API Reference
Python API
| 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 |
Go API
| 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.
Type Conversion
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.
Thread Safety
GoPyLink operations are thread-safe. Multiple goroutines/threads can safely call cross-language functions concurrently.
Performance Notes
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
Best Practices
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.
License
MIT