# ######################### LICENSE ############################ #
# Copyright (c) 2005-2018, 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.
Decorator module, see http://pypi.python.org/pypi/decorator for the documentation. """
return cls.__init__ else: FullArgSpec = collections.namedtuple( 'FullArgSpec', 'args varargs varkw defaults ' 'kwonlyargs kwonlydefaults annotations')
def getfullargspec(f): "A quick and dirty replacement for getfullargspec for Python 2.X" return FullArgSpec._make(inspect.getargspec(f) + ([], None, {}))
def get_init(cls): return cls.__init__.__func__
except AttributeError: # let's assume there are no coroutine functions in old Python def iscoroutinefunction(f): return False
# basic functionality """ 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. """
# Atomic get-and-increment provided by the GIL
# make pylint happy
defaults=None, doc=None, module=None, funcdict=None): # func can be a class or a callable, but not an instance method self.name = '_lambda_' 'kwonlydefaults'): allargs.append('*' + self.varargs) allshortargs.append('*' + self.varargs) allargs.append('*') # single star syntax allargs.append('%s=None' % a) allshortargs.append('%s=%s' % (a, a)) allargs.append('**' + self.varkw) allshortargs.append('**' + self.varkw) # func=None happens when decorating a caller self.defaults = defaults self.dict = funcdict # check existence required attributes raise TypeError('You are decorating a non function: %s' % func)
"Update the signature of func with the data in self" except AttributeError: # for IronPython and similar implementations callermodule = '?' else:
"Make a new function from a given template and update the signature" raise SyntaxError('not a valid function template\n%s' % src) self.shortsignature.split(',')]) raise NameError('%s is overridden in\n%s' % (n, src))
# Ensure each generated function has a unique filename for profilers # (such as cProfile) that depend on the tuple of (<filename>, # <definition line>, <function name>) being unique. except Exception: print('Error in generated code:', file=sys.stderr) print(src, file=sys.stderr) raise
doc=None, module=None, addsource=True, **attrs): """ 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. """ else: # a function body = ('async def %(name)s(%(signature)s):\n' + ibody).replace( 'return', 'return await') else:
""" decorate(func, caller) decorates a function using a caller. """ ex = '_e%d_' % i evaldict[ex] = extra es += ex + ', ' func, "return _call_(_func_, %s%%(shortsignature)s)" % es, evaldict, __wrapped__=func)
"""decorator(caller) converts a caller function into a decorator""" # this is obsolete behavior; you should use decorate instead return decorate(_func, caller) # else return a decorator function 'factories of %s objects' % (caller.__name__, caller.__name__) name = '_lambda_' else: else: # assume caller is an object with a __call__ method name = caller.__class__.__name__.lower() doc = caller.__call__.__doc__ '%s(%s func)' % (name, defaultargs), 'if func is None: return lambda func: _decorate_(func, _call, (%s))\n' 'return _decorate_(func, _call, (%s))' % (defaultargs, defaultargs), evaldict, doc=doc, module=caller.__module__, __wrapped__=caller)
# ####################### contextmanager ####################### #
except ImportError: # Python >= 2.5 from contextlib import GeneratorContextManager as _GeneratorContextManager
"""Context manager decorator""" return FunctionMaker.create( func, "with _self_: return _func_(%(shortsignature)s)", dict(_self_=self, _func_=func), __wrapped__=func)
def __init__(self, g, *a, **k): return _GeneratorContextManager.__init__(self, g(*a, **k)) ContextManager.__init__ = __init__ pass return _GeneratorContextManager.__init__(self, g, a, k)
# Enable Pylint config: contextmanager-decorators=decorator.contextmanager return _contextmanager(func)
# ############################ dispatch_on ############################ #
""" Append ``a`` to the list of the virtual ancestors, unless it is already included. """ add = True for j, va in enumerate(vancestors): if issubclass(va, a): add = False break if issubclass(a, va): vancestors[j] = a add = False if add: vancestors.append(a)
# inspired from simplegeneric by P.J. Eby and functools.singledispatch """ Factory of decorators turning a function into a generic function dispatching on the given arguments. """ assert dispatch_args, 'No dispatch args passed' dispatch_str = '(%s,)' % ', '.join(dispatch_args)
def check(arguments, wrong=operator.ne, msg=''): """Make sure one passes the expected number of arguments""" if wrong(len(arguments), len(dispatch_args)): raise TypeError('Expected %d arguments, got %d%s' % (len(dispatch_args), len(arguments), msg))
def gen_func_dec(func): """Decorator turning a function into a generic function"""
# first check the dispatch arguments argset = set(getfullargspec(func).args) if not set(dispatch_args) <= argset: raise NameError('Unknown dispatch arguments %s' % dispatch_str)
typemap = {}
def vancestors(*types): """ Get a list of sets of virtual ancestors for the given types """ check(types) ras = [[] for _ in range(len(dispatch_args))] for types_ in typemap: for t, type_, ra in zip(types, types_, ras): if issubclass(t, type_) and type_ not in t.mro(): append(type_, ra) return [set(ra) for ra in ras]
def ancestors(*types): """ Get a list of virtual MROs, one for each type """ check(types) lists = [] for t, vas in zip(types, vancestors(*types)): n_vas = len(vas) if n_vas > 1: raise RuntimeError( 'Ambiguous dispatch for %s: %s' % (t, vas)) elif n_vas == 1: va, = vas mro = type('t', (t, va), {}).mro()[1:] else: mro = t.mro() lists.append(mro[:-1]) # discard t and object return lists
def register(*types): """ Decorator to register an implementation for the given types """ check(types)
def dec(f): check(getfullargspec(f).args, operator.lt, ' in ' + f.__name__) typemap[types] = f return f return dec
def dispatch_info(*types): """ An utility to introspect the dispatch algorithm """ check(types) lst = [] for anc in itertools.product(*ancestors(*types)): lst.append(tuple(a.__name__ for a in anc)) return lst
def _dispatch(dispatch_args, *args, **kw): types = tuple(type(arg) for arg in dispatch_args) try: # fast path f = typemap[types] except KeyError: pass else: return f(*args, **kw) combinations = itertools.product(*ancestors(*types)) next(combinations) # the first one has been already tried for types_ in combinations: f = typemap.get(types_) if f is not None: return f(*args, **kw)
# else call the default implementation return func(*args, **kw)
return FunctionMaker.create( func, 'return _f_(%s, %%(shortsignature)s)' % dispatch_str, dict(_f_=_dispatch), register=register, default=func, typemap=typemap, vancestors=vancestors, ancestors=ancestors, dispatch_info=dispatch_info, __wrapped__=func)
gen_func_dec.__name__ = 'dispatch_on' + dispatch_str return gen_func_dec |