decorator_msim.py
146 lines
| 5.5 KiB
| text/x-python
|
PythonLexer
Fernando Perez
|
r1420 | ## The basic trick is to generate the source code for the decorated function | ||
## with the right signature and to evaluate it. | ||||
## Uncomment the statement 'print >> sys.stderr, func_src' in _decorate | ||||
## to understand what is going on. | ||||
__all__ = ["decorator", "update_wrapper", "getinfo"] | ||||
import inspect, sys | ||||
def getinfo(func): | ||||
""" | ||||
Returns an info dictionary containing: | ||||
- name (the name of the function : str) | ||||
- argnames (the names of the arguments : list) | ||||
- defaults (the values of the default arguments : tuple) | ||||
- signature (the signature : str) | ||||
- doc (the docstring : str) | ||||
- module (the module name : str) | ||||
- dict (the function __dict__ : str) | ||||
>>> def f(self, x=1, y=2, *args, **kw): pass | ||||
>>> info = getinfo(f) | ||||
>>> info["name"] | ||||
'f' | ||||
>>> info["argnames"] | ||||
['self', 'x', 'y', 'args', 'kw'] | ||||
>>> info["defaults"] | ||||
(1, 2) | ||||
>>> info["signature"] | ||||
'self, x, y, *args, **kw' | ||||
""" | ||||
assert inspect.ismethod(func) or inspect.isfunction(func) | ||||
regargs, varargs, varkwargs, defaults = inspect.getargspec(func) | ||||
argnames = list(regargs) | ||||
if varargs: | ||||
argnames.append(varargs) | ||||
if varkwargs: | ||||
argnames.append(varkwargs) | ||||
signature = inspect.formatargspec(regargs, varargs, varkwargs, defaults, | ||||
formatvalue=lambda value: "")[1:-1] | ||||
return dict(name=func.__name__, argnames=argnames, signature=signature, | ||||
defaults = func.func_defaults, doc=func.__doc__, | ||||
module=func.__module__, dict=func.__dict__, | ||||
globals=func.func_globals, closure=func.func_closure) | ||||
def update_wrapper(wrapper, wrapped, create=False): | ||||
""" | ||||
An improvement over functools.update_wrapper. By default it works the | ||||
same, but if the 'create' flag is set, generates a copy of the wrapper | ||||
with the right signature and update the copy, not the original. | ||||
Moreovoer, 'wrapped' can be a dictionary with keys 'name', 'doc', 'module', | ||||
'dict', 'defaults'. | ||||
""" | ||||
if isinstance(wrapped, dict): | ||||
infodict = wrapped | ||||
else: # assume wrapped is a function | ||||
infodict = getinfo(wrapped) | ||||
assert not '_wrapper_' in infodict["argnames"], \ | ||||
'"_wrapper_" is a reserved argument name!' | ||||
if create: # create a brand new wrapper with the right signature | ||||
src = "lambda %(signature)s: _wrapper_(%(signature)s)" % infodict | ||||
# import sys; print >> sys.stderr, src # for debugging purposes | ||||
wrapper = eval(src, dict(_wrapper_=wrapper)) | ||||
try: | ||||
wrapper.__name__ = infodict['name'] | ||||
except: # Python version < 2.4 | ||||
pass | ||||
wrapper.__doc__ = infodict['doc'] | ||||
wrapper.__module__ = infodict['module'] | ||||
wrapper.__dict__.update(infodict['dict']) | ||||
wrapper.func_defaults = infodict['defaults'] | ||||
return wrapper | ||||
# the real meat is here | ||||
def _decorator(caller, func): | ||||
infodict = getinfo(func) | ||||
argnames = infodict['argnames'] | ||||
assert not ('_call_' in argnames or '_func_' in argnames), \ | ||||
'You cannot use _call_ or _func_ as argument names!' | ||||
src = "lambda %(signature)s: _call_(_func_, %(signature)s)" % infodict | ||||
dec_func = eval(src, dict(_func_=func, _call_=caller)) | ||||
return update_wrapper(dec_func, func) | ||||
def decorator(caller, func=None): | ||||
""" | ||||
General purpose decorator factory: takes a caller function as | ||||
input and returns a decorator with the same attributes. | ||||
A caller function is any function like this:: | ||||
def caller(func, *args, **kw): | ||||
# do something | ||||
return func(*args, **kw) | ||||
Here is an example of usage: | ||||
>>> @decorator | ||||
... def chatty(f, *args, **kw): | ||||
... print "Calling %r" % f.__name__ | ||||
... return f(*args, **kw) | ||||
>>> chatty.__name__ | ||||
'chatty' | ||||
>>> @chatty | ||||
... def f(): pass | ||||
... | ||||
>>> f() | ||||
Calling 'f' | ||||
For sake of convenience, the decorator factory can also be called with | ||||
two arguments. In this casem ``decorator(caller, func)`` is just a | ||||
shortcut for ``decorator(caller)(func)``. | ||||
""" | ||||
if func is None: # return a decorator function | ||||
return update_wrapper(lambda f : _decorator(caller, f), caller) | ||||
else: # return a decorated function | ||||
return _decorator(caller, func) | ||||
if __name__ == "__main__": | ||||
import doctest; doctest.testmod() | ||||
####################### LEGALESE ################################## | ||||
## Redistributions of source code must retain the above copyright | ||||
## notice, this list of conditions and the following disclaimer. | ||||
## Redistributions in bytecode form must reproduce the above copyright | ||||
## notice, this list of conditions and the following disclaimer in | ||||
## the documentation and/or other materials provided with the | ||||
## distribution. | ||||
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
## HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | ||||
## INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | ||||
## BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS | ||||
## OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||||
## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR | ||||
## TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE | ||||
## USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH | ||||
## DAMAGE. | ||||