From 8d3cd578f9c0a36d29411c96fa70402a7a56d502 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sun, 8 Nov 2015 15:27:22 +0000 Subject: [PATCH] MAINT: update decorators.py module to version 4.0.5 This is commit d6abda0 at https://github.com/micheles/decorator/tree/4.0.5 --- scipy/_lib/decorator.py | 380 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 293 insertions(+), 87 deletions(-) diff --git a/scipy/_lib/decorator.py b/scipy/_lib/decorator.py index 07d9d21..05f7056 100644 --- a/scipy/_lib/decorator.py +++ b/scipy/_lib/decorator.py @@ -1,48 +1,52 @@ -########################## LICENCE ############################### -## -## Copyright (c) 2005-2011, Michele Simionato -## All rights reserved. -## -## 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. +# ######################### LICENSE ############################ # + +# Copyright (c) 2005-2015, 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. """ +from __future__ import print_function -from __future__ import division, print_function, absolute_import - -__version__ = '3.3.2' - -__all__ = ["decorator", "FunctionMaker"] - -import sys import re +import sys import inspect -from functools import partial +import operator +import itertools +import collections -from scipy._lib.six import exec_ +__version__ = '4.0.5' if sys.version >= '3': from inspect import getfullargspec + + def get_init(cls): + return cls.__init__ else: class getfullargspec(object): "A quick and dirty replacement for getfullargspec for Python 2.X" @@ -51,7 +55,6 @@ else: inspect.getargspec(f) self.kwonlyargs = [] self.kwonlydefaults = None - self.annotations = getattr(f, '__annotations__', {}) def __iter__(self): yield self.args @@ -59,17 +62,35 @@ else: yield self.varkw yield self.defaults -DEF = re.compile('\s*def\s*([_\w][_\w\d]*)\s*\(') + getargspec = inspect.getargspec + + def get_init(cls): + return cls.__init__.__func__ + +# getargspec has been deprecated in Python 3.5 +ArgSpec = collections.namedtuple( + 'ArgSpec', 'args varargs varkw defaults') -# basic functionality +def getargspec(f): + """A replacement for inspect.getargspec""" + spec = getfullargspec(f) + return ArgSpec(spec.args, spec.varargs, spec.varkw, spec.defaults) +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. """ + + # Atomic get-and-increment provided by the GIL + _compile_count = itertools.count() + def __init__(self, func=None, name=None, signature=None, defaults=None, doc=None, module=None, funcdict=None): self.shortsignature = signature @@ -82,22 +103,32 @@ class FunctionMaker(object): self.module = func.__module__ if inspect.isfunction(func): argspec = getfullargspec(func) + self.annotations = getattr(func, '__annotations__', {}) for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs', - 'kwonlydefaults', 'annotations'): + 'kwonlydefaults'): setattr(self, a, getattr(argspec, a)) for i, arg in enumerate(self.args): setattr(self, 'arg%d' % i, arg) - self.signature = inspect.formatargspec( - formatvalue=lambda val: "", *argspec)[1:-1] - allargs = list(self.args) - if self.varargs: - allargs.append('*' + self.varargs) - if self.varkw: - allargs.append('**' + self.varkw) - try: - self.shortsignature = ', '.join(allargs) - except TypeError: # exotic signature, valid only in Python 2.X - self.shortsignature = self.signature + if sys.version < '3': # easy way + self.shortsignature = self.signature = ( + inspect.formatargspec( + formatvalue=lambda val: "", *argspec)[1:-1]) + else: # Python 3 way + allargs = list(self.args) + allshortargs = list(self.args) + if self.varargs: + allargs.append('*' + self.varargs) + allshortargs.append('*' + self.varargs) + elif self.kwonlyargs: + allargs.append('*') # single star syntax + for a in self.kwonlyargs: + allargs.append('%s=None' % a) + allshortargs.append('%s=%s' % (a, a)) + if self.varkw: + allargs.append('**' + self.varkw) + allshortargs.append('**' + self.varkw) + self.signature = ', '.join(allargs) + self.shortsignature = ', '.join(allshortargs) self.dict = func.__dict__.copy() # func=None happens when decorating a caller if name: @@ -122,12 +153,15 @@ class FunctionMaker(object): func.__name__ = self.name func.__doc__ = getattr(self, 'doc', None) func.__dict__ = getattr(self, 'dict', {}) - if sys.version_info[0] >= 3: - func.__defaults__ = getattr(self, 'defaults', ()) - else: - func.func_defaults = getattr(self, 'defaults', ()) + func.__defaults__ = getattr(self, 'defaults', ()) func.__kwdefaults__ = getattr(self, 'kwonlydefaults', None) - callermodule = sys._getframe(3).f_globals.get('__name__', '?') + func.__annotations__ = getattr(self, 'annotations', None) + try: + frame = sys._getframe(3) + except AttributeError: # for IronPython and similar implementations + callermodule = '?' + else: + callermodule = frame.f_globals.get('__name__', '?') func.__module__ = getattr(self, 'module', callermodule) func.__dict__.update(kw) @@ -140,16 +174,20 @@ class FunctionMaker(object): raise SyntaxError('not a valid function template\n%s' % src) name = mo.group(1) # extract the function name names = set([name] + [arg.strip(' *') for arg in - self.shortsignature.split(',')]) + self.shortsignature.split(',')]) for n in names: if n in ('_func_', '_call_'): raise NameError('%s is overridden in\n%s' % (n, src)) if not src.endswith('\n'): # add a newline just for safety src += '\n' # this is needed in old versions of Python + + # Ensure each generated function has a unique filename for profilers + # (such as cProfile) that depend on the tuple of (, + # , ) being unique. + filename = '' % (next(self._compile_count),) try: - code = compile(src, '', 'single') - # print >> sys.stderr, 'Compiling %s' % src - exec_(code, evaldict) + code = compile(src, filename, 'single') + exec(code, evaldict) except: print('Error in generated code:', file=sys.stderr) print(src, file=sys.stderr) @@ -165,9 +203,9 @@ class FunctionMaker(object): 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. + 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) @@ -180,37 +218,205 @@ class FunctionMaker(object): self = cls(func, name, signature, defaults, doc, module) ibody = '\n'.join(' ' + line for line in body.splitlines()) return self.make('def %(name)s(%(signature)s):\n' + ibody, - evaldict, addsource, **attrs) + evaldict, addsource, **attrs) -def decorator(caller, func=None): +def decorate(func, caller): """ - decorator(caller) converts a caller function into a decorator; - decorator(caller, func) decorates a function using a caller. + decorate(func, caller) decorates a function using a caller. """ - if func is not None: # returns a decorated function - if sys.version_info[0] >= 3: - evaldict = func.__globals__.copy() + evaldict = func.__globals__.copy() + evaldict['_call_'] = caller + evaldict['_func_'] = func + fun = FunctionMaker.create( + func, "return _call_(_func_, %(shortsignature)s)", + evaldict, __wrapped__=func) + if hasattr(func, '__qualname__'): + fun.__qualname__ = func.__qualname__ + return fun + + +def decorator(caller, _func=None): + """decorator(caller) converts a caller function into a decorator""" + if _func is not None: # return a decorated function + # this is obsolete behavior; you should use decorate instead + return decorate(_func, caller) + # else return a decorator function + if inspect.isclass(caller): + name = caller.__name__.lower() + callerfunc = get_init(caller) + doc = 'decorator(%s) converts functions/generators into ' \ + 'factories of %s objects' % (caller.__name__, caller.__name__) + elif inspect.isfunction(caller): + if caller.__name__ == '': + name = '_lambda_' else: - evaldict = func.func_globals.copy() - evaldict['_call_'] = caller - evaldict['_func_'] = func + name = caller.__name__ + callerfunc = caller + doc = caller.__doc__ + else: # assume caller is an object with a __call__ method + name = caller.__class__.__name__.lower() + callerfunc = caller.__call__.__func__ + doc = caller.__call__.__doc__ + evaldict = callerfunc.__globals__.copy() + evaldict['_call_'] = caller + evaldict['_decorate_'] = decorate + return FunctionMaker.create( + '%s(func)' % name, 'return _decorate_(func, _call_)', + evaldict, doc=doc, module=caller.__module__, + __wrapped__=caller) + + +# ####################### contextmanager ####################### # + +try: # Python >= 3.2 + from contextlib import _GeneratorContextManager +except ImportError: # Python >= 2.5 + from contextlib import GeneratorContextManager as _GeneratorContextManager + + +class ContextManager(_GeneratorContextManager): + def __call__(self, func): + """Context manager decorator""" return FunctionMaker.create( - func, "return _call_(_func_, %(shortsignature)s)", - evaldict, undecorated=func, __wrapped__=func) - else: # returns a decorator - if isinstance(caller, partial): - return partial(decorator, caller) - # otherwise assume caller is a function - first = inspect.getargspec(caller)[0][0] # first arg - if sys.version_info[0] >= 3: - evaldict = caller.__globals__.copy() - else: - evaldict = caller.func_globals.copy() - evaldict['_call_'] = caller - evaldict['decorator'] = decorator + func, "with _self_: return _func_(%(shortsignature)s)", + dict(_self_=self, _func_=func), __wrapped__=func) + +init = getfullargspec(_GeneratorContextManager.__init__) +n_args = len(init.args) +if n_args == 2 and not init.varargs: # (self, genobj) Python 2.7 + def __init__(self, g, *a, **k): + return _GeneratorContextManager.__init__(self, g(*a, **k)) + ContextManager.__init__ = __init__ +elif n_args == 2 and init.varargs: # (self, gen, *a, **k) Python 3.4 + pass +elif n_args == 4: # (self, gen, args, kwds) Python 3.5 + def __init__(self, g, *a, **k): + return _GeneratorContextManager.__init__(self, g, a, k) + ContextManager.__init__ = __init__ + +contextmanager = decorator(ContextManager) + + +# ############################ dispatch_on ############################ # + +def append(a, vancestors): + """ + 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 +def dispatch_on(*dispatch_args): + """ + 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( - '%s(%s)' % (caller.__name__, first), - 'return decorator(_call_, %s)' % first, - evaldict, undecorated=caller, __wrapped__=caller, - doc=caller.__doc__, module=caller.__module__) + 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 -- 2.6.3