问题
I have almost what I want...
This dynamic object encapsulating a generic function call with a dynamic docstring generation:
def add_docs(tool):
def desc(func):
func.__doc__ = "Showing help for %s()" % tool
return func
return desc
class Dynamic(object):
def __getattr__(self, value):
@add_docs(value)
def mutable_f(*args, **kwargs):
print "Calling:", value
print "With arguments:", args, kwargs
return mutable_f
And it works as expected:
>>> Dynamic().test(1, input='file')
Calling: test
With arguments: (1,) {'input': 'file'}
>>> Dynamic().test.__doc__
'Showing help for test()'
The only two problems are that the help show the mutable_f
signature
>>> help(Dynamic().test)
Help on function mutable_f in module __main__:
mutable_f(*args, **kwargs)
Showing help for test()
(END)
And that there's no auto-completion (I can get a list of valid functions on-the-fly, and cache it because that operation is expensive)
I think the first one is unsolvable, but I'm not so sure about the second one. Ideas?
回答1:
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.
来源:https://stackoverflow.com/questions/13603088/python-dynamic-help-and-autocomplete-generation