在Python中使用SimpleXMLRPCServer和**kwargs

15

我有一个类希望使用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,然后再转发到远程服务器。问题出在服务中有一个接受命名变长参数的方法:

@useDb
def select(self, db, fields, **kwargs):
    pass

@useDb 装饰器包装函数,在调用之前创建 db 并打开它,在返回结果之前关闭它。

当我调用此方法时,出现错误“call()收到意外的关键字参数'name'”。那么,是否可以远程调用带有变量命名参数的方法?还是我必须为我需要的每种方法变化创建一个覆盖。

谢谢反馈。我稍微改了一下代码,所以问题不再是问题了。如果我确实需要实现位置参数并支持远程调用,那么现在我知道了这个问题。我认为结合 Thomas 和 praptaks 的方法会很好。通过 xmlrpclient 将 kwargs 转换为位置参数并在服务器端对方法进行包装以解压缩位置参数。

5个回答

16

由于普通的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})

这个想法很好,但是如果有多个api调用层级(比如server.level.test()),事情就不会顺利进行。在这种情况下,__getattr__函数将无法知道“level”不是一个函数调用,并返回一个代理函数,这也会失败。但这是一个好的想法,朝着正确的方向!最终我采用了这个解决方案,以及对Thomas Wouters下面的答案进行了修改。谢谢! - rsmoorthy

4
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

它使用猴子补丁,因为这是迄今为止最容易的方法,由于ServerProxy类中一些笨拙的模块全局变量和名称混淆属性(例如__request)的使用。


什么是monkeypatching?快速的谷歌搜索没有给我一个定义,只有使用装饰器的例子。 - Staale
1
Monkeypatching是指在不改变代码源的情况下修改模块或其他代码片段。请参阅http://en.wikipedia.org/wiki/Monkey_patch。 - Thomas Wouters
猴子补丁通常被认为是有害的。 - Florian Bösch
确实如此。但是,请尝试在不使用它的情况下替换xmlrpclib中使用的'_Method'类。请 :-) 您会发现,比猴子补丁更有害的事情还有很多。 - Thomas Wouters
1
不错的解决方案。只是一个小问题:没有必要限制只传递位置参数或关键字参数之一。我最终发送了这样的参数:("hello", "world", "**kwargs", "text", "Browser"),其实就是 ("hello", "world", text="Browser")。服务器会自动转换这个参数。如果有人愿意进行猴子补丁,可以按照Thomas Wouters的建议,或者简单地以扩展形式调用它。 - rsmoorthy
显示剩余2条评论

1
据我所知,底层协议不支持命名的可变参数(或任何命名参数)。解决此问题的方法是创建一个包装器,将**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)

免责声明:此代码展示了一般思路,您可以稍微优化它(例如编写一个装饰器来实现)。


1

利用上述建议,我创建了一些可工作的代码。

服务器方法包装器:

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

我已测试过,它支持带有固定、位置和关键字参数的方法。


我不喜欢代码侵入其他模块。我倾向于完全包裹其他代码,这样更灵活、更易懂,也不太可能出现意外情况。 - Florian Bösch
我同意,但仍然是一个Python新手,所以只是尝试最少阻力的路径。而且,由于目前一切都在同一个项目中,所以这不是什么大问题。 - Staale

0

正如Thomas Wouters所说,XML-RPC没有关键字参数。只有参数的顺序与协议相关,它们在XML中可以被称为任何名称:arg0、arg1、arg2是完全可以的,同样的参数也可以被称为cheese、candy和bacon。

也许你应该重新考虑使用这个协议的方式?使用类似文档/文字SOAP的东西比其他答案中提出的解决方法要好得多。当然,这可能不可行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接