Autocompletion most often makes use of the output of the dir()
function, which can be hooked. Simply implement a __dir__()
method:
def __dir__(self):
res = dir(type(self)) + list(self.__dict__.keys())
res.extend(['dynamic1', 'dynamic2'])
return res
As for wrapping a function while matching it's signature, you'll need to build a facade based on that signature. I've done exactly that for a Zope security feature:
import inspect
import functools
class _Default(object):
def __init__(self, repr):
self._repr = repr
def __repr__(self):
return self._repr
def _buildFacade(name, spec, docstring):
"""Build a facade function, matching the decorated method in signature.
Note that defaults are replaced by instances of _Default, and _curried
will reconstruct these to preserve mutable defaults.
"""
args = inspect.formatargspec(
formatvalue=lambda v: '=_Default({0!r})'.format(repr(v)), *spec)
callargs = inspect.formatargspec(formatvalue=lambda v: '', *spec)
return 'def {0}{1}:\n """{2}"""\n return _curried{3}'.format(
name, args, docstring, callargs)
def add_docs(tool):
spec = inspect.getargspec(tool)
args, defaults = spec[0], spec[3]
arglen = len(args)
if defaults is not None:
defaults = zip(args[arglen - len(defaults):], defaults)
arglen -= len(defaults)
def _curried(*args, **kw):
# Reconstruct keyword arguments
if defaults is not None:
args, kwparams = args[:arglen], args[arglen:]
for positional, (key, default) in zip(kwparams, defaults):
if isinstance(positional, _Default):
kw[key] = default
else:
kw[key] = positional
return tool(*args, **kw)
name = tool.__name__
doc = 'Showing help for {0}()'.format(name)
facade_globs = dict(_curried=_curried, _Default=_Default)
exec _buildFacade(name, spec, doc) in facade_globs
wrapped = facade_globs[name]
wrapped = functools.update_wrapper(wrapped, tool,
assigned=filter(lambda w: w != '__doc__', functools.WRAPPER_ASSIGNMENTS))
return facade_globs[name]
This will do the correct thing when it comes to method signatures, almost. You cannot get around the mutable defaults here, and need to handle those explicitly to preserve them.
A small demonstration:
>>> def foo(bar, spam='eggs', foobarred={}):
... foobarred[bar] = spam
... print foobarred
...
>>> documented = add_docs(foo)
>>> help(documented)
Help on function foo:
foo(bar, spam='eggs', foobarred={})
Showing help for foo()
>>> documented('monty', 'python')
{'monty': 'python'}
>>> documented('Eric', 'Idle')
{'Eric': 'Idle', 'monty': 'python'}
The whole _Default
dance is required to preserve mutable defaults, which, although a generally a bad idea, do need to continue to work as originally intended. The facade built will look just like the original, and will act like it, but mutables continue to live in the 'correct' location.
Note that the facade gets updated to match the original as closely as possible; by using functools.update_wrapper
various pieces of metadata are copied over from the original to the facade, but we take care to exclude the __doc__
string from that, since our facade explicitly uses it's own docstring instead.