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

1# ######################### LICENSE ############################ # 

2 

3# Copyright (c) 2005-2015, Michele Simionato 

4# All rights reserved. 

5 

6# Redistribution and use in source and binary forms, with or without 

7# modification, are permitted provided that the following conditions are 

8# met: 

9 

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. 

16 

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. 

29 

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 

40 

41from inspect import getfullargspec 

42 

43__version__ = '4.0.5' 

44 

45 

46def get_init(cls): 

47 return cls.__init__ 

48 

49 

50# getargspec has been deprecated in Python 3.5 

51ArgSpec = collections.namedtuple( 

52 'ArgSpec', 'args varargs varkw defaults') 

53 

54 

55def getargspec(f): 

56 """A replacement for inspect.getargspec""" 

57 spec = getfullargspec(f) 

58 return ArgSpec(spec.args, spec.varargs, spec.varkw, spec.defaults) 

59 

60 

61DEF = re.compile(r'\s*def\s*([_\w][_\w\d]*)\s*\(') 

62 

63 

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 """ 

71 

72 # Atomic get-and-increment provided by the GIL 

73 _compile_count = itertools.count() 

74 

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) 

126 

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) 

143 

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 

159 

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 

176 

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) 

198 

199 

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 

213 

214 

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) 

244 

245 

246# ####################### contextmanager ####################### # 

247 

248try: # Python >= 3.2 

249 from contextlib import _GeneratorContextManager 

250except ImportError: # Python >= 2.5 

251 from contextlib import GeneratorContextManager as _GeneratorContextManager 

252 

253 

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) 

260 

261 

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__ 

274 

275contextmanager = decorator(ContextManager) 

276 

277 

278# ############################ dispatch_on ############################ # 

279 

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) 

295 

296 

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) 

305 

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)) 

311 

312 def gen_func_dec(func): 

313 """Decorator turning a function into a generic function""" 

314 

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) 

319 

320 typemap = {} 

321 

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] 

333 

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 

352 

353 def register(*types): 

354 """ 

355 Decorator to register an implementation for the given types 

356 """ 

357 check(types) 

358 

359 def dec(f): 

360 check(getfullargspec(f).args, operator.lt, ' in ' + f.__name__) 

361 typemap[types] = f 

362 return f 

363 return dec 

364 

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 

373 

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) 

388 

389 # else call the default implementation 

390 return func(*args, **kw) 

391 

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) 

397 

398 gen_func_dec.__name__ = 'dispatch_on' + dispatch_str 

399 return gen_func_dec