【问题标题】:Using **kwargs with SimpleXMLRPCServer in python在 python 中使用 **kwargs 和 SimpleXMLRPCServer
【发布时间】:2010-09-12 07:09:58
【问题描述】:

我有一个类,我希望使用 pythons 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 装饰器包装函数,在调用之前创建数据库并打开它,然后在调用完成后关闭它,然后返回结果。

当我调用此方法时,我收到错误“call() got an unexpected keyword argument 'name'”。那么,是否可以远程调用采用变量命名参数的方法?或者我是否必须为我需要的每个方法变体创建一个覆盖。


感谢您的回复。我稍微改变了我的代码,所以这个问题不再是问题。但是,如果我确实需要实现位置参数并支持远程调用,现在我知道这一点以供将来参考。我认为 Thomas 和 praptaks 方法的结合会很好。通过 xmlrpclient 将 kwargs 转换为客户端上的位置参数,并在服务器端的方法上有一个包装器来解包位置参数。

【问题讨论】:

    标签: python simplexmlrpcserver xmlrpclib


    【解决方案1】:

    你不能用普通的 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 函数将不知道“级别”是否不是函数调用,并且也会返回一个代理函数,这会失败。但这是一个好主意,而且方向正确!我最终使用了这个解决方案,以及下面对 Thomas Wouters 答案的修改。谢谢!
    【解决方案2】:

    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 是在不改变代码源的情况下修改模块或其他代码。见en.wikipedia.org/wiki/Monkey_patch
    • 猴子补丁通常被认为是有害的
    • 确实如此。但是,尝试在没有它的情况下替换 xmlrpclib 使用的 '_Method' 类。请 :-) 你会发现有比猴子补丁更有害的东西。
    • 不错的解决方案。只是一个小问题:没有必要限制只传递位置和关键字参数之一。我最终发送了这样的参数:(“hello”,“world”,“**kwargs”,“text”,“Browser”),只不过是(“hello”,“world”,text =“Browser” ”)。服务器会自动翻译这个。如果有人愿意修补补丁,他们可以按照 Thomas Wouters 指出的那样做,或者他们可以简单地以扩展方式调用它。
    【解决方案3】:

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

    免责声明:代码显示了总体思路,您可以将其做得更简洁(例如编写一个装饰器来做到这一点)。

    【讨论】:

      【解决方案4】:

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

      服务器方法包装器:

      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
      

      我对此进行了测试,它支持具有固定、位置和关键字参数的方法。

      【讨论】:

      • 我不喜欢侵入其他模块的代码。我倾向于将其他代码完全包裹起来,这样更灵活、不那么神秘,而且不太可能在你的脸上爆炸。
      • 我同意,但仍然是 python 新手,所以只是尝试阻力最小的路径。而且由于一切都在同一个项目atm中。这不是什么大问题。
      【解决方案5】:

      正如 Thomas Wouters 所说,XML-RPC 没有关键字参数。就协议而言,只有参数的顺序很重要,它们可以在 XML 中调用任何东西:arg0、arg1、arg2 非常好,对于相同的参数,奶酪、糖果和培根也是如此。

      也许您应该重新考虑一下您对协议的使用?使用诸如文档/文字 SOAP 之类的东西比使用其他答案中提供的解决方法要好得多。当然,这可能不可行。

      【讨论】:

        猜你喜欢
        • 2011-04-03
        • 2023-04-01
        • 2011-05-21
        • 2020-09-16
        • 2017-01-28
        • 1970-01-01
        • 1970-01-01
        • 2015-09-16
        • 2018-06-08
        相关资源
        最近更新 更多