Library / SDK
go-python/gopy avatar
go-python/gopy

gopy: compiling a CPython extension module from a Go package

gopy generates a CPython extension module from a go package.

2,335 stars136 forksGoBSD-3-Clause

At a glance

What is it?
gopy reads a Go package and generates the C shim, the wrapper classes and the Python module file needed to call it from CPython, using integer handles instead of raw pointers to stay safe under a moving garbage collector.
Who is it for?
gopy is worth the trouble when you already have a substantial Go library and want to expose it to Python callers without rewriting it, and it is the wrong choice when you need a small clean surface or a library you can install from an index. The int64 handle design is the part that makes it usable at all under modern CPython, and the package layout it generates is good enough that GoGi, a full GUI toolkit, runs through it.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 9 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What gopy actually generates for a Go package

gopy is a code generator, not a runtime library. Point it at an importable Go package and it produces three kinds of artifact: a C shim that talks to the CPython C API, a set of Python wrapper classes, and a pure Python module file that ties them together. The README describes the mechanism as unique int64 handles used to interface with Python, so that no pointers are interchanged. That single design choice is what separates this from a naive cgo wrapper, and it is the reason the project works with current CPython versions rather than fighting the garbage collector.

The mapping between Go and Python types is where most of the work sits. Go structs become Python classes, methods become methods on those classes, and the first embedded struct field is turned into a corresponding Python class inheritance, so a Go type that embeds another type hands its methods and properties to the wrapper automatically. Functions become module level functions. A Go package with several subpackages still links into one common binding library, while each package gets its own `.py` file.

The `gopyh/` directory at the top of the repository tree is the generated handle package, and `bind/` holds the runtime support that the generated code depends on. The `gopy.py` file at the root is the Python side loader. Seeing those three names in the tree tells you the generated code is not throwaway: something in your own Python package will import from `gopyh`, so regenerating bindings has to be part of your build.

Installing the three prerequisites before anything else

The README is upfront that gopy assumes modules based builds and a valid `go.mod` file. The repository's own `go.mod` declares Go 1.22.0, which is worth noticing next to the README's older claim that the tool works with Go 1.15 and above: the module file is the more current statement of what the code actually builds against.

Three things have to be on your machine first, and the README lists them together:

sh
$ python3 -m pip install pybindgen
$ go install golang.org/x/tools/cmd/goimports@latest
$ go install github.com/go-python/gopy@latest

pybindgen generates the low level C to Python bindings. goimports exists because the generator emits import blocks and needs them tidied into the right form. This assumes Go itself is installed and that `~/go/bin` is on your `PATH`.

If you intend to build the Python side as a package, the packaging tools are also required:

sh
python3 -m pip install --upgrade setuptools wheel

The README says pybindgen is the current backend and that adding cffi support would be reasonably straightforward for people running PyPy instead of CPython, with pybindgen expected to be the faster choice on CPython. That is a candid admission that PyPy is not the primary target today.

Four commands that produce four different things

gopy's command set is small but the distinctions matter. From the help output in the README, `pkg` generates and compiles bindings for a Go package and automatically includes subdirectories, also creating the Python files needed to install the module. `exe` does the same thing but makes a standalone executable with the Go packages built in, which is the shape you want when a `-main` argument starts the process. `gen` only generates the C and Python binding files. `build` generates and compiles them.

The README is clear that `pkg` and `exe` are the commands meant for end users, while `gen` and `build` exist for testing the generator itself. Each command has its own help entry, so `gopy help pkg` gives you the argument list rather than a guess.

One option deserves emphasis because the README repeats it twice: `-vm`. Passing `-vm=python3`, or a full path, tells the generator which interpreter to build against. Without it the plain `python` command frequently resolves to Python 2 on older machines, and the README says many errors disappear once you specify it. Any first run should include it.

The generated output is not published to PyPI. The README says that would require handling many Python versions and coordinating with the Go source version, so instead `pkg` writes a Makefile and you run `make install` locally. That is a reasonable trade for a binding generator, but it does mean there is no `pip install` path for a generated package.

Platform setup that the README keeps warning about

Binding a C API from Go is where the platform notes live, and the README is unusually direct about them. On Linux the dynamic linker may not look in the working directory, so a single export is needed:

sh
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.

On Windows the advice is stronger: install Python from python.org and not from the Microsoft Store. The reason given is specific rather than aesthetic. The Store version creates symbolic links for the python executables, and that is incompatible with the `go exec.Command` call gopy uses to run the interpreter. There is a second wrinkle on Windows: the standard installer does not create `python3.exe`, which gopy looks for, so the README points at the well known workaround of copying `python.exe` to `python3.exe`. Linking errors are then addressed with `LIBDIR` or `GOPY_LIBDIR` for the library path and `LIBRARY` or `GOPY_PYLIB` for the library name such as `python39`.

Running the test suite on Windows needs one more package, because Python's built in `resource` module only exists on Unix and the memory leak checks depend on it:

sh
python -m pip install psutil

The `Dockerfile` in the tree still installs `pkg-config` and `python2.7-dev` on top of a `golang:onbuild` base. That is a stale artifact next to a README that says to use `-vm=python3`, and it is a good example of why the SUPPORT_MATRIX.md file in the repository is the file to consult before planning support for a specific Python version.

Where gopy sits against a hand written cgo layer

The honest comparison is not against other Go to Python bridges in the same category, because there are not many. It is against writing the cgo shim yourself. A hand written binding is completely transparent: you see every pointer crossing, you decide the ownership rules, and there is no generator between you and the CPython C API. You also maintain the mapping for every type, every struct field and every signature change, which is exactly the work gopy automates.

The difference in approach is that gopy reflects on the Go package and emits code for it, so the maintenance cost moves from your typing to a regeneration step. That trade pays off when the Go side is large. The README names GoGi as the case where the whole GUI library is usable from Python, and points at a `make; make install` in that project's Python directory followed by the `examples/widgets/widgets.py` demo. A toolkit with widgets, renderers, event loops and thousands of methods is the kind of surface where manual binding would not get finished.

It also pays off in the other direction when you need callbacks. The README lists callback methods from Go into Python as a supported feature: you pass a Python function where a Go function expects a function argument, and Go calls it correctly. Getting that right by hand means handling the GIL on both sides, and v0.4.4 credits a contributor with a GIL release while running Go functions. A generator that handles that consistently across a whole package is a real saving.

Where gopy loses is surface control. Generated Python has the shape of the Go package, not the shape a Python programmer would have chosen, and there is no documented mechanism for renaming or hiding members of a wrapper class. Reviewers who care about API design will notice that immediately.

Release history and what it says about the project's direction

The release record is short and legible. v0.5.0 shipped on 2026-08-25 with Python 3.12 added to the test matrix and improvements to complex conversion, plus a `gopy version` command and a release-please pipeline. The same release appends existing `CFLAGS` and `LDFLAGS` for CGO, which is the kind of change that only matters to people building against a non default toolchain. Between those, v0.4.8 on 2023-12-12 was a bundle of merged pull requests rather than a feature release, and v0.4.4 on 2022-07-01 added the `-build-tags` argument and the GIL release.

The release cadence tells you something about the shape of the work. The tool tracks Go and Python release cycles, and the 2026-08-25 release is the response to Python 3.12 rather than to feature requests. Releases are now cut by release-please from commit history, and the Makefile comments describe that pipeline: merging the release pull request to master bumps `version.go`, tags the commit, publishes the GitHub Release, and GoReleaser builds and uploads binaries. The last push to master was on 2026-09-27, so the tool is being kept current against toolchains rather than developed as a product with a roadmap.

For licensing, the repository is BSD-3-Clause. That matters more than usual here, because generated bindings become part of the Python distribution you ship, and the license obligations attach to code you redistribute rather than to the tool that produced it. Nothing in the README or repository addresses licensing of generated output, so the practical approach is to read LICENSE directly and treat the generated package as ordinary source.

Editorial conclusion

gopy is worth the trouble when you already have a substantial Go library and want to expose it to Python callers without rewriting it, and it is the wrong choice when you need a small clean surface or a library you can install from an index. The int64 handle design is the part that makes it usable at all under modern CPython, and the package layout it generates is good enough that GoGi, a full GUI toolkit, runs through it. The costs are concrete: a C toolchain, pybindgen, goimports, an explicit Python version flag on nearly every invocation, and linker environment variables on Linux. If you take it on, install v0.5.0 from 2026-08-25, read SUPPORT_MATRIX.md before promising support for a Python release, and try `gopy gen` on one package before committing to `gopy pkg` across a tree.

Frequently asked questions

Does gopy work with current Python 3 releases?

The v0.5.0 release published on 2026-08-25 added Python 3.12 to the testing matrix along with improvements to complex conversion. The README still requires modules based builds, a valid `go.mod`, and Go 1.15 or newer, with the repository's own go.mod declaring Go 1.22.0.

How does gopy handle Go pointers when Python calls a function?

The README says gopy uses unique int64 handles to interface with Python, so that no pointers are interchanged between the two runtimes. The stated reason is that this makes the bridge safe for the more recent moving garbage collector, which is the usual source of crashes in naive Go to Python bindings.

How do I install a Python package that gopy generated?

Use the `pkg` command, which creates the Python files needed to install the module along with an auto-generated Makefile, then run `make install` locally. The README notes the packages could theoretically go to PyPI, but says that would require handling many Python versions and coordinating with the Go source version, so local make install is the supported path.

Is gopy still being maintained?

The last push to master was on 2026-09-27, and v0.5.0 was released on 2026-08-25 with a gopy version command and a release-please pipeline for cutting future releases. The Dockerfile still references python2.7-dev, which is out of step with the README's Python 3 guidance.

Official sources

  1. go-python/gopy on GitHub
  2. Issues
  3. License: BSD-3-Clause
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/go-python-gopy.svg)](https://hysenlabs.com/projects/go-python-gopy)