如何创建具有另一个方法签名的新方法
我想知道怎么把一个类的方法签名复制过来,然后在另一个类里创建一个“代理方法”,让它的签名一样。
我正在用Python写一个RPC库。这个服务器支持对服务器端一个类(C)的远程调用。当客户端连接到服务器时,它应该为类C创建一个代理类,方法签名要一样。当程序调用这个代理实例时,它应该在服务器上用相同的参数调用那个方法。
2 个回答
你不需要完全复制函数的参数定义。相反,你可以接受任意数量的位置参数和关键字参数,然后把这些参数传递下去:
def proxy_function(*args, **kw):
return original_function(*args, **kw)
在这里,*args
和 **kw
的写法在 proxy_function
的定义中,分别表示传入函数的参数会被处理成一个元组和一个字典:
>>> def foo(*args, **kw):
... print args
... print kw
...
>>> foo('spam', 'ham', monty='python')
('spam', 'ham')
{'monty': 'python'}
同样,*args
和 **kw
在调用 original_function()
时,分别接收一个序列和一个映射,用来将它们的内容作为单独的参数传递给被调用的函数:
>>> 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]
这样做会生成一个新的函数对象,参数名称与原来的相同,并且处理默认值时会引用原始被代理函数的默认值,而不是将它们复制到外观函数中;这样可以确保即使是可变的默认值也能正常工作。
外观构建器也能处理绑定方法;在这种情况下,self
会在传递之前被移除,以确保目标方法不会收到额外的 self
参数(而且这个参数类型本来就是错的)。
处理未绑定方法不在这里讨论;如果遇到这种情况,你需要提供自己的 _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)
可以考虑使用 boltons.wraps,以下是文档中的一段摘录:
boltons.funcutils.wraps(func, injected=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 还支持根据内部签名来修改外部签名。通过传入一个要注入的参数名称列表,这些参数就会从外部包装函数的签名中移除,这样你的装饰器就可以提供一些不需要传入的参数。
参数: func (function) – 要复制属性的可调用对象。
injected (list) – 一个可选的参数名称列表,这些名称不应该出现在新包装函数的签名中。
update_dict (bool) – 是否将 func 的其他非标准属性复制到包装函数中。默认为 True。
inject_to_varkw (bool) – 当存在 **kwargs 类型的捕获所有参数时,是否忽略缺失的参数。默认为 True。
如果想要更深入地了解函数的包装,可以查看 wraps 所基于的 FunctionBuilder 类型。