在Python中将** kwargs与SimpleXMLRPCServer一起使用
我有一类希望使用python SimpleXMLRPCServer公开为远程服务的类。服务器启动如下所示:
server = SimpleXMLRPCServer((serverSettings.LISTEN_IP,serverSettings.LISTEN_PORT)) service = Service() server.register_instance(service) server.serve_forever()
然后,我有一个如下所示的ServiceRemote类:
def __init__(self,ip,port): self.rpcClient = xmlrpclib.Server('http://%s:%d' %(ip,port)) def __getattr__(self, name): # forward all calls to the rpc client return getattr(self.rpcClient, name)
因此,对ServiceRemote对象的所有调用都将转发到xmlrpclib.Server,然后将其转发到远程服务器。问题是服务中采用名为varargs的方法:
@useDb def select(self, db, fields, **kwargs): pass
@useDb装饰器包装函数,在调用之前创建数据库并打开它,然后在调用完成后关闭它,然后返回结果。
当我调用此方法时,出现错误" call()得到了意外的关键字参数'name'"。因此,是否可以远程调用采用名为实参的变量的方法?还是我必须为我需要的每个方法变体创建一个替代。
感谢答复。我更改了一些代码,因此问题不再是问题。但是,如果我确实确实需要实现位置参数并支持远程调用,那么现在我知道这一点,以供将来参考。我认为将Thomas和praptaks方法结合起来会很好。通过xmlrpclient在客户端上将kwargs转换为位置args,并在服务器端对方法进行包装以解压缩位置参数。
解决方案
据我所知,底层协议不支持命名的varargs(或者与此有关的任何命名的args)。解决方法是创建一个包装器,该包装器将使用** kwargs并将其作为普通字典传递给要调用的方法。像这样
服务器端:
def select_wrapper(self, db, fields, kwargs): """accepts an ordinary dict which can pass through xmlrpc""" return select(self,db,fields, **kwargs)
在客户端:
def select(self, db, fields, **kwargs): """you can call it with keyword arguments and they will be packed into a dict""" return self.rpcClient.select_wrapper(self,db,fields,kwargs)
免责声明:代码显示了总体思路,我们可以使其更加简洁(例如,编写装饰器来实现)。
XML-RPC实际上没有"关键字参数"的概念,因此xmlrpclib不会尝试支持它们。我们将需要选择一个约定,然后修改xmlrpclib._Method以接受关键字参数并使用该约定传递它们。
例如,我曾经使用过XML-RPC服务器,该服务器在平面列表中将关键字参数作为两个参数传递,"-KEYWORD"后跟实际参数。我不再可以访问编写的代码以从Python访问该XML-RPC服务器,但是它很简单,大致如下:
import xmlrpclib _orig_Method = xmlrpclib._Method class KeywordArgMethod(_orig_Method): def __call__(self, *args, **kwargs): if args and kwargs: raise TypeError, "Can't pass both positional and keyword args" args = list(args) for key in kwargs: args.append('-%s' % key.upper()) args.append(kwargs[key]) return _orig_Method.__call__(self, *args) xmlrpclib._Method = KeywordArgMethod
它使用Monkeypatching,因为到目前为止,这是最简单的方法,因为在ServerProxy类中对模块全局变量和名称混杂属性(例如__request)进行了一些笨拙的使用。
正如Thomas Wouters所说,XML-RPC没有关键字参数。就协议而言,仅参数的顺序很重要,可以在XML中将其称为任何东西:arg0,arg1,arg2非常好,相同参数的奶酪,糖果和培根也是如此。
也许我们应该简单地重新考虑对协议的使用?使用诸如文档/文字SOAP之类的东西比诸如此处其他答案中提出的替代方法那样要好得多。当然,这可能不可行。
根据以上建议,我创建了一些工作代码。
服务器方法包装器:
def unwrap_kwargs(func): def wrapper(*args, **kwargs): print args if args and isinstance(args[-1], list) and len(args[-1]) == 2 and "kwargs" == args[-1][0]: func(*args[:-1], **args[-1][1]) else: func(*args, **kwargs) return wrapper
客户端设置(执行一次):
_orig_Method = xmlrpclib._Method class KeywordArgMethod(_orig_Method): def __call__(self, *args, **kwargs): args = list(args) if kwargs: args.append(("kwargs", kwargs)) return _orig_Method.__call__(self, *args) xmlrpclib._Method = KeywordArgMethod
我对此进行了测试,它支持带有固定,位置和关键字参数的方法。
我们不能使用纯xmlrpc来执行此操作,因为它没有关键字参数的概念。但是,我们可以将其作为xmlrpc之上的协议进行叠加,该协议将始终将列表作为第一个参数传递,将字典作为第二个参数传递,然后提供适当的支持代码,因此这对于用法而言是透明的,例如以下示例:
服务器
from SimpleXMLRPCServer import SimpleXMLRPCServer class Server(object): def __init__(self, hostport): self.server = SimpleXMLRPCServer(hostport) def register_function(self, function, name=None): def _function(args, kwargs): return function(*args, **kwargs) _function.__name__ = function.__name__ self.server.register_function(_function, name) def serve_forever(self): self.server.serve_forever() #example usage server = Server(('localhost', 8000)) def test(arg1, arg2): print 'arg1: %s arg2: %s' % (arg1, arg2) return 0 server.register_function(test) server.serve_forever()
客户
import xmlrpclib class ServerProxy(object): def __init__(self, url): self._xmlrpc_server_proxy = xmlrpclib.ServerProxy(url) def __getattr__(self, name): call_proxy = getattr(self._xmlrpc_server_proxy, name) def _call(*args, **kwargs): return call_proxy(args, kwargs) return _call #example usage server = ServerProxy('http://localhost:8000') server.test(1, 2) server.test(arg2=2, arg1=1) server.test(1, arg2=2) server.test(*[1,2]) server.test(**{'arg1':1, 'arg2':2})