How to create a new method signed by another

How can I copy a method signature from one class and create a "proxy method" with the same signature in another?

I am writing an RPC library in python. The server supports remote calls to the server class (C). When the client connects to the server, it must create a proxy class for C with the same signatures. When a program calls a proxy instance, it must call a function with the same arguments on the server.

+3


source to share


2 answers


You don't need to copy the function signature. Instead, take arbitrary positional and keyword arguments and pass them in:

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

      

Here, the syntax *args

and **kw

in the signature proxy_function

are specified by the tuple and dictionary, respectively, of the arguments passed to the function:

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

      

Likewise, the syntax *args

and **kw

in the call original_function()

accepts a sequence or collation, respectively, in order to apply their contents as separate arguments to the called function:

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

      

Combined, these two serve as an arbitrary argument to proxy functions.



Creating a complete facade, on the other hand, is a little more important:

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 creates a whole new function object with the same argument names and processes the defaults by referencing the original defaults, rather than copying them to the facade; this ensures that even modified defaults continue to work.

The facade constructor also handles related methods; in this case, it self

is removed from the call before passing it to ensure that the target method does not receive an additional argument self

(which would be wrong anyway).

There is no handling of unrelated methods; provide your own function _proxy

in this case, which can instantiate the proxied class and pass no arguments self

or provide a new value for self

; you cannot go through self

without changes.

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)

      

+2


source


Consider using boltons.wraps - here's an excerpt from the documentation:



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

Modeling after the built-in functools.wraps (), this function is used to make your wrapping decorators mirrored wrapped Functions:

Name Signature of the documentation module

The built-in functools.wraps () copies the first three, but don't copy the signature. This version of the wrappers can copy the inner one exactly, allowing introspection. Usage is identical to inline 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 bolt version of the wrappers supports modification of the outer signature based on the inner signature. By skipping the list of argument names entered, those arguments will be removed from the outer signature, allowing your decorator to provide arguments that aren't passed.

Parameters: func (function) - the callee whose attributes are copied.

injected (list) - An optional list of argument names that should not appear in the new wrapper signature.

update_dict (bool) - copy other, non-standard func attributes to the wrapper. The default is True.

inject_to_varkw (bool) - Ignore missing arguments when ** kwargs catch-all type is present. The default is True.

For more detailed handling of functions, see the FunctionBuilder type on which the wrappers were created.

+2


source







All Articles