_decorator.py
220 lines
| 9.1 KiB
| text/x-python
|
PythonLexer
Brian Granger
|
r2268 | ########################## LICENCE ############################### | ||
Fernando Perez
|
r6920 | |||
# Copyright (c) 2005-2012, Michele Simionato | ||||
# All rights reserved. | ||||
# Redistribution and use in source and binary forms, with or without | ||||
# modification, are permitted provided that the following conditions are | ||||
# met: | ||||
# 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. | ||||
Brian Granger
|
r2268 | |||
""" | ||||
Decorator module, see http://pypi.python.org/pypi/decorator | ||||
for the documentation. | ||||
""" | ||||
Fernando Perez
|
r6920 | __version__ = '3.3.3' | ||
__all__ = ["decorator", "FunctionMaker", "partial"] | ||||
import sys, re, inspect | ||||
Brian Granger
|
r2268 | |||
try: | ||||
from functools import partial | ||||
except ImportError: # for Python version < 2.5 | ||||
class partial(object): | ||||
"A simple replacement of functools.partial" | ||||
def __init__(self, func, *args, **kw): | ||||
self.func = func | ||||
Bernardo B. Marques
|
r4872 | self.args = args | ||
Brian Granger
|
r2268 | self.keywords = kw | ||
def __call__(self, *otherargs, **otherkw): | ||||
kw = self.keywords.copy() | ||||
kw.update(otherkw) | ||||
return self.func(*(self.args + otherargs), **kw) | ||||
Fernando Perez
|
r6920 | if sys.version >= '3': | ||
from inspect import getfullargspec | ||||
else: | ||||
class getfullargspec(object): | ||||
"A quick and dirty replacement for getfullargspec for Python 2.X" | ||||
def __init__(self, f): | ||||
self.args, self.varargs, self.varkw, self.defaults = \ | ||||
inspect.getargspec(f) | ||||
self.kwonlyargs = [] | ||||
self.kwonlydefaults = None | ||||
def __iter__(self): | ||||
yield self.args | ||||
yield self.varargs | ||||
yield self.varkw | ||||
yield self.defaults | ||||
Brian Granger
|
r2268 | DEF = re.compile('\s*def\s*([_\w][_\w\d]*)\s*\(') | ||
# basic functionality | ||||
class FunctionMaker(object): | ||||
""" | ||||
An object with the ability to create functions with a given signature. | ||||
It has attributes name, doc, module, signature, defaults, dict and | ||||
methods update and make. | ||||
""" | ||||
def __init__(self, func=None, name=None, signature=None, | ||||
defaults=None, doc=None, module=None, funcdict=None): | ||||
Fernando Perez
|
r6920 | self.shortsignature = signature | ||
Brian Granger
|
r2268 | if func: | ||
# func can be a class or a callable, but not an instance method | ||||
self.name = func.__name__ | ||||
if self.name == '<lambda>': # small hack for lambda functions | ||||
Bernardo B. Marques
|
r4872 | self.name = '_lambda_' | ||
Brian Granger
|
r2268 | self.doc = func.__doc__ | ||
self.module = func.__module__ | ||||
if inspect.isfunction(func): | ||||
Fernando Perez
|
r6920 | argspec = getfullargspec(func) | ||
self.annotations = getattr(func, '__annotations__', {}) | ||||
for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs', | ||||
'kwonlydefaults'): | ||||
setattr(self, a, getattr(argspec, a)) | ||||
Brian Granger
|
r2268 | for i, arg in enumerate(self.args): | ||
setattr(self, 'arg%d' % i, arg) | ||||
Fernando Perez
|
r6920 | if sys.version < '3': # easy way | ||
self.shortsignature = self.signature = \ | ||||
inspect.formatargspec( | ||||
formatvalue=lambda val: "", *argspec)[1:-1] | ||||
else: # Python 3 way | ||||
self.signature = self.shortsignature = ', '.join(self.args) | ||||
if self.varargs: | ||||
self.signature += ', *' + self.varargs | ||||
self.shortsignature += ', *' + self.varargs | ||||
if self.kwonlyargs: | ||||
for a in self.kwonlyargs: | ||||
self.signature += ', %s=None' % a | ||||
self.shortsignature += ', %s=%s' % (a, a) | ||||
if self.varkw: | ||||
self.signature += ', **' + self.varkw | ||||
self.shortsignature += ', **' + self.varkw | ||||
Brian Granger
|
r2268 | self.dict = func.__dict__.copy() | ||
Fernando Perez
|
r6920 | # func=None happens when decorating a caller | ||
Brian Granger
|
r2268 | if name: | ||
self.name = name | ||||
if signature is not None: | ||||
self.signature = signature | ||||
if defaults: | ||||
self.defaults = defaults | ||||
if doc: | ||||
self.doc = doc | ||||
if module: | ||||
self.module = module | ||||
if funcdict: | ||||
self.dict = funcdict | ||||
# check existence required attributes | ||||
assert hasattr(self, 'name') | ||||
if not hasattr(self, 'signature'): | ||||
raise TypeError('You are decorating a non function: %s' % func) | ||||
def update(self, func, **kw): | ||||
"Update the signature of func with the data in self" | ||||
func.__name__ = self.name | ||||
func.__doc__ = getattr(self, 'doc', None) | ||||
func.__dict__ = getattr(self, 'dict', {}) | ||||
func.func_defaults = getattr(self, 'defaults', ()) | ||||
Fernando Perez
|
r6920 | func.__kwdefaults__ = getattr(self, 'kwonlydefaults', None) | ||
func.__annotations__ = getattr(self, 'annotations', None) | ||||
Brian Granger
|
r2268 | callermodule = sys._getframe(3).f_globals.get('__name__', '?') | ||
func.__module__ = getattr(self, 'module', callermodule) | ||||
func.__dict__.update(kw) | ||||
def make(self, src_templ, evaldict=None, addsource=False, **attrs): | ||||
"Make a new function from a given template and update the signature" | ||||
src = src_templ % vars(self) # expand name and signature | ||||
evaldict = evaldict or {} | ||||
mo = DEF.match(src) | ||||
if mo is None: | ||||
raise SyntaxError('not a valid function template\n%s' % src) | ||||
name = mo.group(1) # extract the function name | ||||
Fernando Perez
|
r6920 | names = set([name] + [arg.strip(' *') for arg in | ||
self.shortsignature.split(',')]) | ||||
for n in names: | ||||
if n in ('_func_', '_call_'): | ||||
Brian Granger
|
r2268 | raise NameError('%s is overridden in\n%s' % (n, src)) | ||
if not src.endswith('\n'): # add a newline just for safety | ||||
Fernando Perez
|
r6920 | src += '\n' # this is needed in old versions of Python | ||
Brian Granger
|
r2268 | try: | ||
code = compile(src, '<string>', 'single') | ||||
Fernando Perez
|
r6920 | # print >> sys.stderr, 'Compiling %s' % src | ||
Brian Granger
|
r2268 | exec code in evaldict | ||
except: | ||||
print >> sys.stderr, 'Error in generated code:' | ||||
print >> sys.stderr, src | ||||
raise | ||||
func = evaldict[name] | ||||
if addsource: | ||||
attrs['__source__'] = src | ||||
self.update(func, **attrs) | ||||
return func | ||||
@classmethod | ||||
def create(cls, obj, body, evaldict, defaults=None, | ||||
Fernando Perez
|
r6920 | doc=None, module=None, addsource=True, **attrs): | ||
Brian Granger
|
r2268 | """ | ||
Create a function from the strings name, signature and body. | ||||
evaldict is the evaluation dictionary. If addsource is true an attribute | ||||
__source__ is added to the result. The attributes attrs are added, | ||||
if any. | ||||
""" | ||||
if isinstance(obj, str): # "name(signature)" | ||||
name, rest = obj.strip().split('(', 1) | ||||
Bernardo B. Marques
|
r4872 | signature = rest[:-1] #strip a right parens | ||
Brian Granger
|
r2268 | func = None | ||
else: # a function | ||||
name = None | ||||
signature = None | ||||
func = obj | ||||
Fernando Perez
|
r6920 | self = cls(func, name, signature, defaults, doc, module) | ||
Brian Granger
|
r2268 | ibody = '\n'.join(' ' + line for line in body.splitlines()) | ||
Fernando Perez
|
r6920 | return self.make('def %(name)s(%(signature)s):\n' + ibody, | ||
Brian Granger
|
r2268 | evaldict, addsource, **attrs) | ||
Bernardo B. Marques
|
r4872 | |||
Brian Granger
|
r2268 | def decorator(caller, func=None): | ||
""" | ||||
decorator(caller) converts a caller function into a decorator; | ||||
decorator(caller, func) decorates a function using a caller. | ||||
""" | ||||
if func is not None: # returns a decorated function | ||||
Fernando Perez
|
r6920 | evaldict = func.func_globals.copy() | ||
evaldict['_call_'] = caller | ||||
evaldict['_func_'] = func | ||||
Brian Granger
|
r2268 | return FunctionMaker.create( | ||
Fernando Perez
|
r6920 | func, "return _call_(_func_, %(shortsignature)s)", | ||
evaldict, undecorated=func, __wrapped__=func) | ||||
Brian Granger
|
r2268 | else: # returns a decorator | ||
if isinstance(caller, partial): | ||||
return partial(decorator, caller) | ||||
# otherwise assume caller is a function | ||||
Fernando Perez
|
r6920 | first = inspect.getargspec(caller)[0][0] # first arg | ||
evaldict = caller.func_globals.copy() | ||||
evaldict['_call_'] = caller | ||||
evaldict['decorator'] = decorator | ||||
Brian Granger
|
r2268 | return FunctionMaker.create( | ||
Fernando Perez
|
r6920 | '%s(%s)' % (caller.__name__, first), | ||
'return decorator(_call_, %s)' % first, | ||||
evaldict, undecorated=caller, __wrapped__=caller, | ||||
Brian Granger
|
r2268 | doc=caller.__doc__, module=caller.__module__) | ||