前言
Python 和 Go 语言是两种不同的编程语言,它们分别有自己的优势和适用场景。在一些项目中,由于团队内已有的技术栈或者某一部分业务的需求,可能需要 Python 和 Go 相互调用,以此来提升效率和性能。
- 性能优势
Go 通常比 Python 更高效,尤其是在并发和并行处理方面。因此,可以使用 Go 编写高性能的底层组件或服务,并通过 Python 调用这些组件来提高整体性能。
- 并发和并行处理
Go 是为并发设计的语言,具有轻量级线程(goroutines)和通道(channels)等特性。在需要处理大量并发任务的情况下,Go 的并发性能可能优于 Python。通过将 Go 组件嵌入到 Python 代码中,可以利用 Go 的并发处理能力。
- 易用性和灵活性
Python 具有简洁、易读、易学的语法,适用于快速开发和原型设计。将 Python 用于高层逻辑和算法,而使用 Go 来编写性能敏感的底层组件,可以在性能和开发速度之间找到平衡。
方案简介
1.动态库调用
将go代码编译成so库 -> python中通过ctypes引用so库并指定需要调用的函数(同时可指定传入参数类型和返回值类型) -> 指定后按python使用函数方式调用。
需要注意的是:python和go之间参数传递是需要经过C的数据类型转换的,因此需要了解python中ctypes数据类型和python数据类型以及C的数据类型对应关系
三种数据类型使用场景:
- ctypes数据类型为指定调用函数时的传入参数和返回值的数据类型
- python数据类型为调用函数时传入的参数的数据类型
- C的数据类型为go代码中定义的函数所需的参数和返回值数据类型
二、示例
假设我们就有这么一个函数,需要在 Python 中调用这个函数
func add(a int, b int) int {
return a + b
}
第一步:对此函数进行改造
如下:
// main.go
package main
import "C"
func main() {}
//export add
func add(a int, b int) int {
return a + b
}
1.import “C” 这个必须要加载 Go 源文件前,这一点必须做,应该就是告诉编译器我要即将编译的软件需要做为 C 的库而不直接是二进制。这个包也提供一些功能让 Go 去直接操作 C 的数据结构等等。
2.main() main 函数一定不能少,即使没有任何一行代码也没事;
3.//export add 在函数定义之前添加上注释来告诉编译器哪些定义可以被 C 引用,注意 // 和 export 之前不能有空格,否则会导出失败的
第二步: 将 Go 编译成 C 可以调用的库
执行命令
go build --buildmode=c-shared -o library.so main.go
编译完后在当前目录下回有一个 library.so 和 library.h 的文件
第三步:python调用
编写python调用函数main.py
import ctypes
lib = ctypes.cdll.LoadLibrary("library.so")
print(lib.add(1, 2))
由于Python和Go是两种不同的语言,其参数的类型也有所不同。所以在调用时需要进一步转换成C语言类型来进行转换。
import ctypes
lib = ctypes.cdll.LoadLibrary("library.so")
GoInt64 = ctypes.c_int64
GoInt = GoInt64
add = lib.add
add.argtypes = [GoInt64, GoInt64]
add.restype = GoInt64
res = add(GoInt(1), GoInt(2))
print(res)
使用 ctypes.cdll.LoadLibrary 来加载这个动态库,然后就可以直接调用了。
其对应参数类型如下:
例如:当python传入的参数需是string时,ctypes中指定的传参参数类型需为c_wchar_p,go中需要指定接收的参数数据类型为 *C.wchar_t。
其他类型请参考文档链接https://docs.python.org/3.5/library/ctypes.html
2.grpc调用
grpc已经在之前文章https://blog.csdn.net/qq_45066628/article/details/118602349介绍过了,就不重复赘述了。
调用流程
Python gRPC
1.环境安装
grpcio 是启动 gRPC 服务的项目依赖
pip install grpcio
grpcio 是启动 gRPC 服务的项目依赖
pip install grpcio-tools
2.定义 proto 文件
syntax = "proto3";
import "google/protobuf/empty.proto";
// service 关键字定义提供的服务
service MyService {
// 定义一个探活方法
rpc Health (.google.protobuf.Empty) returns (.google.protobuf.Empty){
}
// 定义一个批量查询 user 的方法
rpc User (UserReq) returns (UserReply){
}
}
// message 关键字定义交互的数据结构
message UserReq {
repeated int32 userIDs= 1;
}
message UserReply {
string message = 1;
// repeated 定义一个数组
repeated User data = 2;
}
message User {
string name = 1;
int32 age = 2;
string email = 3;
}
3.编译生成代码
使用 protoc 和相应的插件可以编译生成对应语言的代码
-I 指定 import 路径,可以指定多个 -I 参数,编译时按顺序查找,不指定默认当前目录
python -m grpc_tools.protoc -I ./ —python_out=. —grpc_python_out=. ./api.proto
经过上述步骤,我们生成了这样两个文件api_pb2.py 此文件包含每个 message 生成一个含有静态描述符的模块,,该模块与一个元类(metaclass)在运行时(runtime)被用来创建所需的Python数据访问类api_pb2_grpc.py 此文件包含生成的 客户端(MyServiceStub)和服务端 (MyServiceServicer)的类。
4.实现python服务端
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging
from concurrent import futures
import grpc
from api import api_pb2_grpc, api_pb2
from api.api_pb2_grpc import MyServiceServicer
from service import get_users
class Service(MyServiceServicer):
def Health(self, request, context):
return
def User(self, request, context):
print('start to process request...')
res = get_users(request.userIDs)
users = []
for u in res:
users.append(api_pb2.User(name=u['name'], age=u['age'], email=u['email']))
return api_pb2.UserReply(message='success', data=users)
def serve():
print('start grpc server====>')
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
api_pb2_grpc.add_MyServiceServicer_to_server(Service(), server)
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()
if __name__ == '__main__':
logging.basicConfig()
serve()
Go gRPC
Go 服务作为客户端调用 Python 服务,同样需要根据 proto 文件生成代码,进而创建客户端发起 RPC。
1.环境搭建
安装 ptotobuf, 推荐使用 brew
brew install protobuf
protoc go 插件安装
go get -u github.com/golang/protobuf/protoc-gen-go
这里安装在 GOPATH 下的 bin 目录,所以保证这个目录在 $PATH 中
export PATH=“P A T H : PATH:PATH:(go env GOPATH)/bin”
代码 gprc 依赖安装
go get -u google.golang.org/grpc
2.生成 Go pb 代码
protoc -I ./ —go_out=plugins=grpc:./ api.proto
3.Go客户端调用
package main
import (
"context"
"fmt"
"log"
"time"
"ginDemo/api"
"google.golang.org/grpc"
)
const (
address = "localhost:50051"
defaultName = "world"
)
func main() {
conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := api.NewMyServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.User(ctx, &api.UserReq{UserIDs: []int32{1, 2}})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
fmt.Printf("gprc result: %+v", r.Data)
}
评论(0)
您还未登录,请登录后发表或查看评论