How can I copy the signature of a method from one class, and create a "proxy method" with same signature in another ?.
I am writing a RPC library in python.
The server supports remote calls to a server-side class (C).
When the client connects to the server, it should create a proxy class for C with same signatures.
When the program calls proxy instance, it should call the function with same arguments at the server.
Consider using boltons.wraps - here's an excerpt from the documentation:
boltons.funcutils.wraps(func, injected=None, **kw)
Modeled after the built-in functools.wraps(), this function is used to
make your decorator’s wrapper functions reflect the wrapped
function’s:
Name Documentation Module Signature
The built-in functools.wraps() copies the first three, but does not
copy the signature. This version of wraps can copy the inner
function’s signature exactly, allowing seamless usage and
introspection. Usage is identical to the built-in version:
>>> 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'
In addition, the boltons version of wraps supports modifying the outer
signature based on the inner signature. By passing a list of injected
argument names, those arguments will be removed from the outer
wrapper’s signature, allowing your decorator to provide arguments that
aren’t passed in.
Parameters: func (function) – The callable whose attributes are to
be copied.
injected (list) – An optional list of argument names which should not
appear in the new wrapper’s signature.
update_dict (bool) – Whether to copy other, non-standard attributes of
func over to the wrapper. Defaults to True.
inject_to_varkw (bool) – Ignore missing arguments when a **kwargs-type
catch-all is present. Defaults to True.
For more in-depth wrapping of functions, see the FunctionBuilder type,
on which wraps was built.
You don't have to copy the function signature. Instead, accept arbitrary positional and keyword arguments and pass those on:
def proxy_function(*args, **kw):
return original_function(*args, **kw)
Here the *args
and **kw
syntax in the proxy_function
signature are given a tuple and dictionary, respectively, of arguments passed into the function:
>>> def foo(*args, **kw):
... print args
... print kw
...
>>> foo('spam', 'ham', monty='python')
('spam', 'ham')
{'monty': 'python'}
Similarly, the *args
and **kw
syntax in the original_function()
call takes a sequence or a mapping, respectively, to apply their contents as separate arguments to the function being called:
>>> def bar(baz, fourtytwo=42):
... print baz
... print fourtytwo
...
>>> args = ('hello world!',)
>>> kwargs = {'fourtytwo': 'the answer'}
>>> bar(*args, **kwargs)
hello world!
the answer
Combined, the two serve as an arbitrary-argument-pass-through for proxy functions.
Creating a full facade on the other hand is a little more involved:
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]
This produces a whole new function object with the same argument names, and handles defaults by referencing the original proxied function defaults rather than copy them to the facade; this ensures that even mutable defaults continue to work.
The facade builder handles bound methods too; in that case self
is removed from the call before passing on, to ensure that the target method is not getting an extra self
argument (which would be the wrong type anyway).
Handling unbound methods is out of scope here; provide your own _proxy
function in that case that can instantiate the proxied class and pass on arguments without the self
or provide a new value for self
; you cannot pass in self
unaltered.
Demo:
>>> 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)