【问题标题】:how to create a new method with signature of another如何创建具有另一个签名的新方法
【发布时间】:2014-08-23 08:32:03
【问题描述】:

如何从一个类中复制方法的签名,并在另一个类中创建具有相同签名的“代理方法”?

我正在用 python 编写一个 RPC 库。 服务器支持对服务器端类 (C) 的远程调用。 当客户端连接到服务器时,它应该为 C 创建一个具有相同签名的代理类。 当程序调用代理实例时,它应该在服务器上调用具有相同参数的函数。

【问题讨论】:

    标签: python function python-2.7 proxy


    【解决方案1】:

    考虑使用boltons.wraps - 这是文档的摘录:

    boltons.funcutils.wraps(func, injection=None, **kw)

    仿照内置的functools.wraps(),该函数用于 让你的装饰器的包装函数反映被包装的 函数:

    命名文档模块签名

    内置的 functools.wraps() 复制前三个,但没有 复制签名。这个版本的wraps可以复制内部 函数的签名准确,允许无缝使用和 内省。用法与内置版本相同:

    >>> from boltons.funcutils import wraps
    >>>
    >>> def print_return(func):
    ...     @wraps(func)
    ...     def wrapper(*args, **kwargs):
    ...         ret = func(*args, **kwargs)
    ...         print(ret)
    ...         return ret
    ...     return wrapper
    ...
    >>> @print_return
    ... def example():
    ...     '''docstring'''
    ...     return 'example return value'
    >>>
    >>> val = example()
    example return value
    >>> example.__name__
    'example'
    >>> example.__doc__
    'docstring'
    

    另外,boltons 版本的 wraps 支持修改外层 基于内部签名的签名。通过传递一个注入列表 参数名称,这些参数将从外部删除 包装器的签名,允许您的装饰器提供参数 没有传入。

    Parameters: func (function) – 其属性的可调用对象 被复制。

    injected (list) – 参数名称的可选列表,不应该 出现在新包装器的签名中。

    update_dict (bool) – 是否复制其他非标准属性 func 到包装器。默认为真。

    inject_to_varkw (bool) – **kwargs 类型时忽略缺少的参数 包罗万象。默认为真。

    对于函数的更深入的包装,请参见 FunctionBuilder 类型, 包裹是建立在哪个之上的。

    【讨论】:

      【解决方案2】:

      你没有复制函数签名。相反,接受任意位置和关键字参数并将其传递:

      def proxy_function(*args, **kw):
          return original_function(*args, **kw)
      

      这里 proxy_function 签名中的 *args**kw 语法分别被赋予了传递给函数的参数的元组和字典:

      >>> def foo(*args, **kw):
      ...     print args
      ...     print kw
      ... 
      >>> foo('spam', 'ham', monty='python')
      ('spam', 'ham')
      {'monty': 'python'}
      

      同样,original_function() 调用中的*args**kw 语法分别采用序列或映射,将它们的内容作为单独的参数应用到被调用的函数:

      >>> def bar(baz, fourtytwo=42):
      ...     print baz
      ...     print fourtytwo
      ... 
      >>> args = ('hello world!',)
      >>> kwargs = {'fourtytwo': 'the answer'}
      >>> bar(*args, **kwargs)
      hello world!
      the answer
      

      两者结合起来,作为代理函数的任意参数传递。

      另一方面,创建一个完整的外观会涉及更多:

      import inspect
      
      _default = object()
      
      def build_facade(func):
          """Build a facade function, matching the signature of `func`.
      
          Note that defaults are replaced by _default, and _proxy will reconstruct
          these to preserve mutable defaults.
      
          """
          name = func.__name__
          docstring = func.__doc__
          spec = inspect.getargspec(func)
          args, defaults = spec[0], spec[3]
          boundmethod = getattr(func, '__self__', None)
      
          arglen = len(args)
          if defaults is not None:
              defaults = zip(args[arglen - len(defaults):], defaults)
              arglen -= len(defaults)
      
          def _proxy(*args, **kw):
              if boundmethod:
                  args = args[1:]  # assume we are bound too and don't pass on self
              # Reconstruct keyword arguments
              if defaults is not None:
                  args, kwparams = args[:arglen], args[arglen:]
                  for positional, (key, default) in zip(kwparams, defaults):
                      if positional is _default:
                          kw[key] = default
                      else:
                          kw[key] = positional
              return func(*args, **kw)
      
          args = inspect.formatargspec(formatvalue=lambda v: '=_default', *spec)
          callargs = inspect.formatargspec(formatvalue=lambda v: '', *spec)
      
          facade = 'def {}{}:\n    """{}"""\n    return _proxy{}'.format(
              name, args, docstring, callargs)
          facade_globs = {'_proxy': _proxy, '_default': _default}
          exec facade in facade_globs
          return facade_globs[name]
      

      这会产生一个具有相同参数名称的全新函数对象,并通过引用原始代理函数默认值而不是将它们复制到外观来处理默认值;这确保即使是可变的默认值也能继续工作。

      facade builder 也处理 bound 方法;在这种情况下,self 在传递之前从调用中删除,以确保目标方法没有获得额外的 self 参数(无论如何这将是错误的类型)。

      处理 unbound 方法超出了这里的范围;在这种情况下提供您自己的_proxy 函数,该函数可以实例化代理类并在没有self 的情况下传递参数或为self 提供新值;你不能原封不动地传入self

      演示:

      >>> def foobar(bar, baz, default=[]):
      ...     print bar, baz, default
      ... 
      >>> build_facade(foobar)
      <function foobar at 0x10258df50>
      >>> build_facade(foobar)('spam', 'eggs')
      spam eggs []
      >>> inspect.getargspec(build_facade(foobar))
      ArgSpec(args=['bar', 'baz', 'default'], varargs=None, keywords=None, defaults=(<object object at 0x102593cd0>,))
      >>> class Foo(object):
      ...     def bar(self, spam): print spam
      ... 
      >>> foo = Foo()
      >>> class FooProxy(object):
      ...     bar = build_facade(foo.bar)
      ... 
      >>> FooProxy().bar('hello!')
      hello!
      >>> inspect.getargspec(FooProxy.bar)
      ArgSpec(args=['self', 'spam'], varargs=None, keywords=None, defaults=None)
      

      【讨论】:

      • 谢谢,但我想复制签名。当我输入“help(proxy.foo)”时,我会看到函数的完整描述。
      • @dk7:为什么需要复制签名?你在反省结果吗?为什么要反省结果?
      • @dk7:这里的技术是绝大多数 RPC 库使用的技术;示例:stdlib xmlrpclib method objectPySimpleSoap callpython-jsonrpc client call function
      • 我希望应用程序在调试模式下使用 python 控制台运行时更加用户友好。
      • 晚会,但供将来参考:Martijin 方法有效,我相信这是 pythonic 方法。为了适应 dk7 的需要,可以通过设置 proxy.__doc__ = orig_func.__doc__ 动态添加文档字符串。有关更多详细信息,请参阅stackoverflow.com/questions/4056983/…
      猜你喜欢
      • 1970-01-01
      • 2020-09-01
      • 1970-01-01
      • 2011-09-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-01
      • 2021-12-16
      相关资源
      最近更新 更多