Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/scipy/_lib/decorator.py: 57%
235 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-12 06:31 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-12 06:31 +0000
1# ######################### LICENSE ############################ #
3# Copyright (c) 2005-2015, Michele Simionato
4# All rights reserved.
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are
8# met:
10# Redistributions of source code must retain the above copyright
11# notice, this list of conditions and the following disclaimer.
12# Redistributions in bytecode form must reproduce the above copyright
13# notice, this list of conditions and the following disclaimer in
14# the documentation and/or other materials provided with the
15# distribution.
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21# HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
22# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
23# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
24# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
26# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
27# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
28# DAMAGE.
30"""
31Decorator module, see https://pypi.python.org/pypi/decorator
32for the documentation.
33"""
34import re
35import sys
36import inspect
37import operator
38import itertools
39import collections
41from inspect import getfullargspec
43__version__ = '4.0.5'
46def get_init(cls):
47 return cls.__init__
50# getargspec has been deprecated in Python 3.5
51ArgSpec = collections.namedtuple(
52 'ArgSpec', 'args varargs varkw defaults')
55def getargspec(f):
56 """A replacement for inspect.getargspec"""
57 spec = getfullargspec(f)
58 return ArgSpec(spec.args, spec.varargs, spec.varkw, spec.defaults)
61DEF = re.compile(r'\s*def\s*([_\w][_\w\d]*)\s*\(')
64# basic functionality
65class FunctionMaker:
66 """
67 An object with the ability to create functions with a given signature.
68 It has attributes name, doc, module, signature, defaults, dict, and
69 methods update and make.
70 """
72 # Atomic get-and-increment provided by the GIL
73 _compile_count = itertools.count()
75 def __init__(self, func=None, name=None, signature=None,
76 defaults=None, doc=None, module=None, funcdict=None):
77 self.shortsignature = signature
78 if func:
79 # func can be a class or a callable, but not an instance method
80 self.name = func.__name__
81 if self.name == '<lambda>': # small hack for lambda functions
82 self.name = '_lambda_'
83 self.doc = func.__doc__
84 self.module = func.__module__
85 if inspect.isfunction(func):
86 argspec = getfullargspec(func)
87 self.annotations = getattr(func, '__annotations__', {})
88 for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs',
89 'kwonlydefaults'):
90 setattr(self, a, getattr(argspec, a))
91 for i, arg in enumerate(self.args):
92 setattr(self, 'arg%d' % i, arg)
93 allargs = list(self.args)
94 allshortargs = list(self.args)
95 if self.varargs:
96 allargs.append('*' + self.varargs)
97 allshortargs.append('*' + self.varargs)
98 elif self.kwonlyargs:
99 allargs.append('*') # single star syntax
100 for a in self.kwonlyargs:
101 allargs.append('%s=None' % a)
102 allshortargs.append('%s=%s' % (a, a))
103 if self.varkw:
104 allargs.append('**' + self.varkw)
105 allshortargs.append('**' + self.varkw)
106 self.signature = ', '.join(allargs)
107 self.shortsignature = ', '.join(allshortargs)
108 self.dict = func.__dict__.copy()
109 # func=None happens when decorating a caller
110 if name:
111 self.name = name
112 if signature is not None:
113 self.signature = signature
114 if defaults:
115 self.defaults = defaults
116 if doc:
117 self.doc = doc
118 if module:
119 self.module = module
120 if funcdict:
121 self.dict = funcdict
122 # check existence required attributes
123 assert hasattr(self, 'name')
124 if not hasattr(self, 'signature'):
125 raise TypeError('You are decorating a non-function: %s' % func)
127 def update(self, func, **kw):
128 "Update the signature of func with the data in self"
129 func.__name__ = self.name
130 func.__doc__ = getattr(self, 'doc', None)
131 func.__dict__ = getattr(self, 'dict', {})
132 func.__defaults__ = getattr(self, 'defaults', ())
133 func.__kwdefaults__ = getattr(self, 'kwonlydefaults', None)
134 func.__annotations__ = getattr(self, 'annotations', None)
135 try:
136 frame = sys._getframe(3)
137 except AttributeError: # for IronPython and similar implementations
138 callermodule = '?'
139 else:
140 callermodule = frame.f_globals.get('__name__', '?')
141 func.__module__ = getattr(self, 'module', callermodule)
142 func.__dict__.update(kw)
144 def make(self, src_templ, evaldict=None, addsource=False, **attrs):
145 "Make a new function from a given template and update the signature"
146 src = src_templ % vars(self) # expand name and signature
147 evaldict = evaldict or {}
148 mo = DEF.match(src)
149 if mo is None:
150 raise SyntaxError('not a valid function template\n%s' % src)
151 name = mo.group(1) # extract the function name
152 names = set([name] + [arg.strip(' *') for arg in
153 self.shortsignature.split(',')])
154 for n in names:
155 if n in ('_func_', '_call_'):
156 raise NameError('%s is overridden in\n%s' % (n, src))
157 if not src.endswith('\n'): # add a newline just for safety
158 src += '\n' # this is needed in old versions of Python
160 # Ensure each generated function has a unique filename for profilers
161 # (such as cProfile) that depend on the tuple of (<filename>,
162 # <definition line>, <function name>) being unique.
163 filename = '<decorator-gen-%d>' % (next(self._compile_count),)
164 try:
165 code = compile(src, filename, 'single')
166 exec(code, evaldict)
167 except: # noqa: E722
168 print('Error in generated code:', file=sys.stderr)
169 print(src, file=sys.stderr)
170 raise
171 func = evaldict[name]
172 if addsource:
173 attrs['__source__'] = src
174 self.update(func, **attrs)
175 return func
177 @classmethod
178 def create(cls, obj, body, evaldict, defaults=None,
179 doc=None, module=None, addsource=True, **attrs):
180 """
181 Create a function from the strings name, signature, and body.
182 evaldict is the evaluation dictionary. If addsource is true, an
183 attribute __source__ is added to the result. The attributes attrs
184 are added, if any.
185 """
186 if isinstance(obj, str): # "name(signature)"
187 name, rest = obj.strip().split('(', 1)
188 signature = rest[:-1] # strip a right parens
189 func = None
190 else: # a function
191 name = None
192 signature = None
193 func = obj
194 self = cls(func, name, signature, defaults, doc, module)
195 ibody = '\n'.join(' ' + line for line in body.splitlines())
196 return self.make('def %(name)s(%(signature)s):\n' + ibody,
197 evaldict, addsource, **attrs)
200def decorate(func, caller):
201 """
202 decorate(func, caller) decorates a function using a caller.
203 """
204 evaldict = func.__globals__.copy()
205 evaldict['_call_'] = caller
206 evaldict['_func_'] = func
207 fun = FunctionMaker.create(
208 func, "return _call_(_func_, %(shortsignature)s)",
209 evaldict, __wrapped__=func)
210 if hasattr(func, '__qualname__'):
211 fun.__qualname__ = func.__qualname__
212 return fun
215def decorator(caller, _func=None):
216 """decorator(caller) converts a caller function into a decorator"""
217 if _func is not None: # return a decorated function
218 # this is obsolete behavior; you should use decorate instead
219 return decorate(_func, caller)
220 # else return a decorator function
221 if inspect.isclass(caller):
222 name = caller.__name__.lower()
223 callerfunc = get_init(caller)
224 doc = 'decorator(%s) converts functions/generators into ' \
225 'factories of %s objects' % (caller.__name__, caller.__name__)
226 elif inspect.isfunction(caller):
227 if caller.__name__ == '<lambda>':
228 name = '_lambda_'
229 else:
230 name = caller.__name__
231 callerfunc = caller
232 doc = caller.__doc__
233 else: # assume caller is an object with a __call__ method
234 name = caller.__class__.__name__.lower()
235 callerfunc = caller.__call__.__func__
236 doc = caller.__call__.__doc__
237 evaldict = callerfunc.__globals__.copy()
238 evaldict['_call_'] = caller
239 evaldict['_decorate_'] = decorate
240 return FunctionMaker.create(
241 '%s(func)' % name, 'return _decorate_(func, _call_)',
242 evaldict, doc=doc, module=caller.__module__,
243 __wrapped__=caller)
246# ####################### contextmanager ####################### #
248try: # Python >= 3.2
249 from contextlib import _GeneratorContextManager
250except ImportError: # Python >= 2.5
251 from contextlib import GeneratorContextManager as _GeneratorContextManager
254class ContextManager(_GeneratorContextManager):
255 def __call__(self, func):
256 """Context manager decorator"""
257 return FunctionMaker.create(
258 func, "with _self_: return _func_(%(shortsignature)s)",
259 dict(_self_=self, _func_=func), __wrapped__=func)
262init = getfullargspec(_GeneratorContextManager.__init__)
263n_args = len(init.args)
264if n_args == 2 and not init.varargs: # (self, genobj) Python 2.7
265 def __init__(self, g, *a, **k):
266 return _GeneratorContextManager.__init__(self, g(*a, **k))
267 ContextManager.__init__ = __init__
268elif n_args == 2 and init.varargs: # (self, gen, *a, **k) Python 3.4
269 pass
270elif n_args == 4: # (self, gen, args, kwds) Python 3.5
271 def __init__(self, g, *a, **k):
272 return _GeneratorContextManager.__init__(self, g, a, k)
273 ContextManager.__init__ = __init__
275contextmanager = decorator(ContextManager)
278# ############################ dispatch_on ############################ #
280def append(a, vancestors):
281 """
282 Append ``a`` to the list of the virtual ancestors, unless it is already
283 included.
284 """
285 add = True
286 for j, va in enumerate(vancestors):
287 if issubclass(va, a):
288 add = False
289 break
290 if issubclass(a, va):
291 vancestors[j] = a
292 add = False
293 if add:
294 vancestors.append(a)
297# inspired from simplegeneric by P.J. Eby and functools.singledispatch
298def dispatch_on(*dispatch_args):
299 """
300 Factory of decorators turning a function into a generic function
301 dispatching on the given arguments.
302 """
303 assert dispatch_args, 'No dispatch args passed'
304 dispatch_str = '(%s,)' % ', '.join(dispatch_args)
306 def check(arguments, wrong=operator.ne, msg=''):
307 """Make sure one passes the expected number of arguments"""
308 if wrong(len(arguments), len(dispatch_args)):
309 raise TypeError('Expected %d arguments, got %d%s' %
310 (len(dispatch_args), len(arguments), msg))
312 def gen_func_dec(func):
313 """Decorator turning a function into a generic function"""
315 # first check the dispatch arguments
316 argset = set(getfullargspec(func).args)
317 if not set(dispatch_args) <= argset:
318 raise NameError('Unknown dispatch arguments %s' % dispatch_str)
320 typemap = {}
322 def vancestors(*types):
323 """
324 Get a list of sets of virtual ancestors for the given types
325 """
326 check(types)
327 ras = [[] for _ in range(len(dispatch_args))]
328 for types_ in typemap:
329 for t, type_, ra in zip(types, types_, ras):
330 if issubclass(t, type_) and type_ not in t.__mro__:
331 append(type_, ra)
332 return [set(ra) for ra in ras]
334 def ancestors(*types):
335 """
336 Get a list of virtual MROs, one for each type
337 """
338 check(types)
339 lists = []
340 for t, vas in zip(types, vancestors(*types)):
341 n_vas = len(vas)
342 if n_vas > 1:
343 raise RuntimeError(
344 'Ambiguous dispatch for %s: %s' % (t, vas))
345 elif n_vas == 1:
346 va, = vas
347 mro = type('t', (t, va), {}).__mro__[1:]
348 else:
349 mro = t.__mro__
350 lists.append(mro[:-1]) # discard t and object
351 return lists
353 def register(*types):
354 """
355 Decorator to register an implementation for the given types
356 """
357 check(types)
359 def dec(f):
360 check(getfullargspec(f).args, operator.lt, ' in ' + f.__name__)
361 typemap[types] = f
362 return f
363 return dec
365 def dispatch_info(*types):
366 """
367 An utility to introspect the dispatch algorithm
368 """
369 check(types)
370 lst = [tuple(a.__name__ for a in anc)
371 for anc in itertools.product(*ancestors(*types))]
372 return lst
374 def _dispatch(dispatch_args, *args, **kw):
375 types = tuple(type(arg) for arg in dispatch_args)
376 try: # fast path
377 f = typemap[types]
378 except KeyError:
379 pass
380 else:
381 return f(*args, **kw)
382 combinations = itertools.product(*ancestors(*types))
383 next(combinations) # the first one has been already tried
384 for types_ in combinations:
385 f = typemap.get(types_)
386 if f is not None:
387 return f(*args, **kw)
389 # else call the default implementation
390 return func(*args, **kw)
392 return FunctionMaker.create(
393 func, 'return _f_(%s, %%(shortsignature)s)' % dispatch_str,
394 dict(_f_=_dispatch), register=register, default=func,
395 typemap=typemap, vancestors=vancestors, ancestors=ancestors,
396 dispatch_info=dispatch_info, __wrapped__=func)
398 gen_func_dec.__name__ = 'dispatch_on' + dispatch_str
399 return gen_func_dec