Runoneall

Runoneall's Blog

首页
关于
链接
RSS
Email


python原生导入无缝衔接go与python

  • 作者: Tom Brown
  • 时间: 2025-06-24
  • 分类: 默认  

地址:https://github.com/FramerOrg/FramerModules/wiki/Module-Document#gobridge

这是一个给 Framer 写的模块,但可以拆开来用:

import sys
import os
import subprocess
import ctypes
from importlib.abc import MetaPathFinder, Loader
from importlib.machinery import ModuleSpec


class GoLoader(Loader):
    def __init__(self, so_path, fullname):
        self.so_path = so_path
        self.fullname = fullname

    def create_module(self, spec):
        return None

    def exec_module(self, module):
        # load library
        lib = ctypes.cdll.LoadLibrary(self.so_path)

        # dynamic function getter
        def get_lib_attr(name):
            return getattr(lib, name)

        # bind to module
        module.__getattr__ = get_lib_attr


class GoFinder(MetaPathFinder):
    def find_spec(self, fullname, path, target=None):
        # get import module name
        module_name = fullname.split(".")[-1]

        # go file search path
        search_paths = sys.path if path is None else path
        for base_path in search_paths:

            # possible file paths
            py_path = os.path.join(base_path, f"{module_name}.py")
            go_path = os.path.join(base_path, f"{module_name}.go")
            so_path = os.path.join(base_path, f"{module_name}.so")

            # ignore python file
            if os.path.isfile(py_path):
                continue

            # process go file
            if os.path.isfile(go_path):

                # ensure newest compiled
                self._ensure_compiled(go_path, so_path)

                # custom module loader
                loader = GoLoader(so_path, fullname)

                # create module spec
                return ModuleSpec(
                    name=fullname, loader=loader, origin=go_path, is_package=True
                )
        return None

    def _ensure_compiled(self, go_path, so_path):
        # build if so file old
        if not os.path.exists(so_path) or (
            os.path.getmtime(go_path) > os.path.getmtime(so_path)
        ):

            # compile go file
            self._compile_go(go_path, so_path)

    def _compile_go(self, go_path, so_path):
        # build command
        cmd = ["go", "build", "-buildmode=c-shared", "-o", so_path, go_path]

        # run command
        try:
            result = subprocess.run(cmd, capture_output=True, text=True, check=True)
            self.logger(f"Compiled {go_path} -> {so_path}")
            if result.stderr:
                self.logger("Compiler warnings: " + result.stderr)

        # catch errors
        except subprocess.CalledProcessError as e:
            raise ImportError(
                f"Go compilation failed for {go_path}:\n"
                f"Exit code: {e.returncode}\n"
                f"Error: {e.stderr}"
            ) from e

        # if go not installed
        except FileNotFoundError:
            raise ImportError("Go compiler not found. Install from https://golang.org/")

sys.meta_path.insert(0, GoFinder())

将其保存为一个 python 文件,然后在项目入口处导入一下就行

首先准备一个叫 mymodule.go 的文件,内容如下:

package main

import "C" //必须引入C库

import "fmt"

//加入下面注释代码,表示导出,可以被python调用

//export PrintDll
func PrintDll() {
	fmt.Println("我来自dll")
}

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

func main() {
	//必须加一个main函数,作为CGO编译的入口,无具体实现代码
}

然后在 python 内可以直接用 import mymodule
会自动将 go 编译为 so/dylib/dll,智能识别更改

每当 go 代码更改后会重新编译,时间较长,编译后下一次就不会重新编译了

以及用作为 python 包也是可以的,目录结构如下:

可以直接用 from testModule import mymodule

理论上任何能编译为共享库的语言都能用这套代码

© 2025 Runoneall. Old Fasion Theme. Powered by Hexo.