python - 如何使我的 Python 程序提供的功能可用于在同一台或其他计算机上运行的以其他语言编写的程序?
问题描述
我创建了一个用 Python 编写的系统,提供各种硬件控制、数据采集和处理功能。我希望这个系统可以从用 Python 或其他语言(如 Lua、Matlab、Octave、C++ 和可能的其他语言)编写的程序访问。此外,这些程序可能在该计算机上或通过网络连接的另一台机器上运行。
我知道存在各种用于 Python 的 RPC 库,例如
- https://pypi.org/project/simple-rpc/(不提供与多种语言的简单接口)
- Ice https://zeroc.com/products/ice(对于我的简单应用程序来说太大了)
是否有任何简单且轻量级的解决方案可以从用其他语言编写的程序远程调用 Python 函数?
解决方案
我在 2012 年开发了一个类似的系统,其中 Python 函数的参数和返回值是通过 MessagePack 传递的。这样的解决方案能够与用各种语言编写的程序进行通信。它发布在 Usenet alt.sources 组https://groups.google.com/g/alt.sources/c/QasPxJkKIUs(也可在 funet 存档中找到http://ftp.funet.fi/pub/archive/alt .sources/2722.gz)。现在我用 ZeroMQ 库对其进行了补充,该库提供与运行在同一台机器或远程机器上的程序的有效通信。
系统的核心是用 Python 编写的服务器:
#!/usr/bin/python3
"""
The code below implements a simple ZeroMQ and MessagePack RPC server.
Written by Wojciech M. Zabolotny, wzab<at>ise.pw.edu.pl or wzab01<at>gmail.com
13.04.2021
The code is based on a post: "Simple object based RPC for Python",
https://groups.google.com/g/alt.sources/c/QasPxJkKIUs/m/An1JnLooNCcJ
https://www.funet.fi/pub/archive/alt.sources/2722.gz
This code is published as PUBLIC DOMAIN or under
Creative Commons Zero v1.0 Universal license (whichever better suits your needs).
"""
import time
import zmq
import msgpack as mp
import traceback
import os
#Functions which process requests
def remote_mult(a,b):
return a*b
def remote_div(a,b):
print(a,b,a/b)
return a/b
def cat_file(fname):
f=open(fname,"rb")
return f.read()
#Table of functions
func={
'mult':remote_mult,
'div':remote_div,
'file':cat_file,
}
def handle(msg):
try:
obj = mp.unpackb(msg)
if len(obj) != 2:
raise Exception("Wrong number of RPC objects, should be 2: name and arguments")
if isinstance(obj[1],tuple) or isinstance(obj[1],list):
res=func[obj[0]](*obj[1])
elif isinstance(obj[1],dict):
res=func[obj[0]](**obj[1])
else:
raise Exception("Wrong type of arguments in RPC, should be list, tuple or dictionary")
res = ("OK", res)
except Exception:
res=("error", traceback.format_exc())
return mp.packb(res)
if __name__ == "__main__":
context = zmq.Context()
socket = context.socket(zmq.REP)
# Create the server, binding to localhost on port 9999
socket.bind("tcp://*:9999")
while True:
msg = socket.recv()
res = handle(msg)
socket.send(res)
服务器可以发布具有不同参数的不同函数。参数可以作为位置参数(然后它们应该在元组中传递)或关键字参数(然后它们应该作为映射/字典传递)给出。
下面给出了使用这些函数的 Python 客户端示例:
#!/usr/bin/python3
"""
The code below implements a simple ZeroMQ and MessagePack RPC client.
Written by Wojciech M. Zabolotny, wzab<at>ise.pw.edu.pl or wzab01<at>gmail.com
13.04.2021
The code is based on a post: "Simple object based RPC for Python",
https://groups.google.com/g/alt.sources/c/QasPxJkKIUs/m/An1JnLooNCcJ
https://www.funet.fi/pub/archive/alt.sources/2722.gz
This code is published as PUBLIC DOMAIN or under
Creative Commons Zero v1.0 Universal license (whichever better suits your needs).
"""
import socket
import sys
import msgpack as p
import zmq
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
objects=[
["mult",(4,5)],
["mult",{"a":7,"b":8}],
["div",{"a":9,"b":4}],
["file",("/etc/passwd",)],
["file",("/etc/non_existing_file",)],
]
context = zmq.Context()
socket = context.socket(zmq.REQ)
socket.connect("tcp://localhost:9999")
for obj in objects:
socket.send(p.packb(obj))
# Get the reply.
msg = socket.recv()
resp = p.unpackb(msg)
print("Received reply", resp)
以下是用其他语言编写的客户端。
在 Lua 中:
-- Demonstrator of the communication with simple Python RPC server from Lua
-- Written by Wojciech M. Zabołotny (wzab01<at>gmail.com or wzab<at>ise.pw.edu.pl)
-- Copyright: This program is released into the public domain or under
-- Creative Commons Zero v1.0 Universal license (whichever better suits your needs).
local zmq = require "lzmq"
--require "utils"
local mp = require "mpack"
--print_version(zmq)
local pr = require "pl.pretty"
context = assert(zmq.context())
rpcsrv = assert(context:socket (zmq.REQ))
assert(rpcsrv:connect("tcp://localhost:9999"))
function rpc(params)
local req=mp.pack(test)
rpcsrv:send(req)
local rcv=rpcsrv:recv()
local res=mp.unpack(rcv)
return res
end
test = {"file",{"/etc/passwd"}}
local res = rpc(test)
pr.dump(res)
test = {"mult",{7,8}}
res = rpc(test)
pr.dump(res)
test = {"div",{b=4.0,a=9.0}}
res = rpc(test)
pr.dump(res)
-- The above works, but 9/4 is printed as 2.
print(res[2])
test = {"div",{a=4.0,b=9.0}}
res = rpc(test)
pr.dump(res)
-- The above works, but 4/9 is printed as 0.
print(res[2])
在八度
% Demonstrator of the communication with simple Python RPC server from Octave
% Written by Wojciech M. Zabołotny (wzab01<at>gmail.com or wzab<at>ise.pw.edu.pl)
% Copyright: This program is released into the public domain.
pkg load jsonlab
pkg load zeromq
srv = zmq_socket (ZMQ_REQ);
zmq_connect (srv, "tcp://localhost:9999");
function res = rpc(req,fname,fargs,maxlen=10000)
x={fname, fargs};
a=savemsgpack(x);
zmq_send(req,a);
w=zmq_recv(req,maxlen);
y=loadmsgpack(char(w));
if strcmp(char(y{1}),"OK")
res = y{2};
end
if strcmp(char(y{1}),"error")
error(char(y{2}));
end
endfunction
res = rpc(srv,"mult",struct("a",13,"b",20));
res
res = rpc(srv,"mult",{17,3});
res
res = rpc(srv,"file",{"/etc/passwd"});
char(res')
最后在 C++ 中
// Demonstrator of the communication with simple Python RPC server from C++
// Written by Wojciech M. Zabołotny (wzab01<at>gmail.com or wzab<at>ise.pw.edu.pl)
// Copyright: This program is released into the public domain or under
// Creative Commons Zero v1.0 Universal license (whichever better suits your needs).
#include <string>
#include <zmq.hpp>
#include <iostream>
#include <msgpack.hpp>
msgpack::object_handle rpc(zmq::socket_t &sock, auto req)
{
std::size_t offset = 0;
std::stringstream rstr;
msgpack::pack(rstr,req);
zmq::message_t msg(rstr.str());
sock.send(msg,zmq::send_flags::none);
auto res = sock.recv(msg, zmq::recv_flags::none);
auto oh = msgpack::unpack((const char *)msg.data(),msg.size(),offset);
return oh;
}
int main(void) {
zmq::context_t ctx;
zmq::socket_t sock(ctx, zmq::socket_type::req);
sock.connect("tcp://localhost:9999");
msgpack::object_handle res;
res = rpc(sock,std::tuple<std::string, std::array<int,2>>({"mult", {7, 8}}));
std::cout << res.get() << std::endl;
res = rpc(sock,std::tuple<std::string, std::map<std::string,int>>({"div", {{"a",8},{"b",7}}}));
std::cout << res.get() << std::endl;
res = rpc(sock,std::tuple<std::string, std::map<std::string,int>>({"div", {{"b",8},{"a",7}}}));
std::cout << res.get() << std::endl;
res = rpc(sock,std::tuple<std::string, std::tuple<std::string>>({ "file", {"/etc/passwd"}}));
std::cout << res.get() << std::endl;
}
上面的 C++ 代码必须使用如下-fconcepts-ts
选项编译:
c++ obj_rpc_cli.cc -o obj_rpc_cli -fconcepts-ts -g -lzmq -lmsgpackc
解决方案真的很小。维护的版本在 github 存储库中可用:https ://gitlab.com/WZab/python-versatile-rpc
推荐阅读
- mongodb - 使用查找字段的 mongodb 查询正在返回意外的单元素数组中的字段值
- javascript - 用另一个数组过滤一个数组并创建新对象?
- c# - 生成多个表 DDL 以及数据和所有约束
- python - 将数据从 SQL Server 2016 导入 Azure Data Lake Gen 2 的方法
- cgal - CGAL:在迭代有限面时识别凸包面
- c# - 在本地调试 Dynamics365 代码活动
- mongodb - 如何从 mongo statefulset yaml 文件运行 rs.init?
- git - Github PR 显示较旧的提交
- reactjs - 开玩笑测试无法导入文件
- laravel-nova - Laravel Nova 设置全局 DateTime 格式