Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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 

41__version__ = '4.0.5' 

42 

43if sys.version >= '3': 

44 from inspect import getfullargspec 

45 

46 def get_init(cls): 

47 return cls.__init__ 

48else: 

49 class getfullargspec(object): 

50 "A quick and dirty replacement for getfullargspec for Python 2.x" 

51 def __init__(self, f): 

52 self.args, self.varargs, self.varkw, self.defaults = \ 

53 inspect.getargspec(f) 

54 self.kwonlyargs = [] 

55 self.kwonlydefaults = None 

56 

57 def __iter__(self): 

58 yield self.args 

59 yield self.varargs 

60 yield self.varkw 

61 yield self.defaults 

62 

63 getargspec = inspect.getargspec 

64 

65 def get_init(cls): 

66 return cls.__init__.__func__ 

67 

68# getargspec has been deprecated in Python 3.5 

69ArgSpec = collections.namedtuple( 

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

71 

72 

73def getargspec(f): 

74 """A replacement for inspect.getargspec""" 

75 spec = getfullargspec(f) 

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

77 

78 

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

80 

81 

82# basic functionality 

83class FunctionMaker(object): 

84 """ 

85 An object with the ability to create functions with a given signature. 

86 It has attributes name, doc, module, signature, defaults, dict, and 

87 methods update and make. 

88 """ 

89 

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

91 _compile_count = itertools.count() 

92 

93 def __init__(self, func=None, name=None, signature=None, 

94 defaults=None, doc=None, module=None, funcdict=None): 

95 self.shortsignature = signature 

96 if func: 

97 # func can be a class or a callable, but not an instance method 

98 self.name = func.__name__ 

99 if self.name == '<lambda>': # small hack for lambda functions 

100 self.name = '_lambda_' 

101 self.doc = func.__doc__ 

102 self.module = func.__module__ 

103 if inspect.isfunction(func): 

104 argspec = getfullargspec(func) 

105 self.annotations = getattr(func, '__annotations__', {}) 

106 for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs', 

107 'kwonlydefaults'): 

108 setattr(self, a, getattr(argspec, a)) 

109 for i, arg in enumerate(self.args): 

110 setattr(self, 'arg%d' % i, arg) 

111 if sys.version < '3': # easy way 

112 self.shortsignature = self.signature = ( 

113 inspect.formatargspec( 

114 formatvalue=lambda val: "", *argspec)[1:-1]) 

115 else: # Python 3 way 

116 allargs = list(self.args) 

117 allshortargs = list(self.args) 

118 if self.varargs: 

119 allargs.append('*' + self.varargs) 

120 allshortargs.append('*' + self.varargs) 

121 elif self.kwonlyargs: 

122 allargs.append('*') # single star syntax 

123 for a in self.kwonlyargs: 

124 allargs.append('%s=None' % a) 

125 allshortargs.append('%s=%s' % (a, a)) 

126 if self.varkw: 

127 allargs.append('**' + self.varkw) 

128 allshortargs.append('**' + self.varkw) 

129 self.signature = ', '.join(allargs) 

130 self.shortsignature = ', '.join(allshortargs) 

131 self.dict = func.__dict__.copy() 

132 # func=None happens when decorating a caller 

133 if name: 

134 self.name = name 

135 if signature is not None: 

136 self.signature = signature 

137 if defaults: 

138 self.defaults = defaults 

139 if doc: 

140 self.doc = doc 

141 if module: 

142 self.module = module 

143 if funcdict: 

144 self.dict = funcdict 

145 # check existence required attributes 

146 assert hasattr(self, 'name') 

147 if not hasattr(self, 'signature'): 

148 raise TypeError('You are decorating a non-function: %s' % func) 

149 

150 def update(self, func, **kw): 

151 "Update the signature of func with the data in self" 

152 func.__name__ = self.name 

153 func.__doc__ = getattr(self, 'doc', None) 

154 func.__dict__ = getattr(self, 'dict', {}) 

155 func.__defaults__ = getattr(self, 'defaults', ()) 

156 func.__kwdefaults__ = getattr(self, 'kwonlydefaults', None) 

157 func.__annotations__ = getattr(self, 'annotations', None) 

158 try: 

159 frame = sys._getframe(3) 

160 except AttributeError: # for IronPython and similar implementations 

161 callermodule = '?' 

162 else: 

163 callermodule = frame.f_globals.get('__name__', '?') 

164 func.__module__ = getattr(self, 'module', callermodule) 

165 func.__dict__.update(kw) 

166 

167 def make(self, src_templ, evaldict=None, addsource=False, **attrs): 

168 "Make a new function from a given template and update the signature" 

169 src = src_templ % vars(self) # expand name and signature 

170 evaldict = evaldict or {} 

171 mo = DEF.match(src) 

172 if mo is None: 

173 raise SyntaxError('not a valid function template\n%s' % src) 

174 name = mo.group(1) # extract the function name 

175 names = set([name] + [arg.strip(' *') for arg in 

176 self.shortsignature.split(',')]) 

177 for n in names: 

178 if n in ('_func_', '_call_'): 

179 raise NameError('%s is overridden in\n%s' % (n, src)) 

180 if not src.endswith('\n'): # add a newline just for safety 

181 src += '\n' # this is needed in old versions of Python 

182 

183 # Ensure each generated function has a unique filename for profilers 

184 # (such as cProfile) that depend on the tuple of (<filename>, 

185 # <definition line>, <function name>) being unique. 

186 filename = '<decorator-gen-%d>' % (next(self._compile_count),) 

187 try: 

188 code = compile(src, filename, 'single') 

189 exec(code, evaldict) 

190 except: # noqa: E722 

191 print('Error in generated code:', file=sys.stderr) 

192 print(src, file=sys.stderr) 

193 raise 

194 func = evaldict[name] 

195 if addsource: 

196 attrs['__source__'] = src 

197 self.update(func, **attrs) 

198 return func 

199 

200 @classmethod 

201 def create(cls, obj, body, evaldict, defaults=None, 

202 doc=None, module=None, addsource=True, **attrs): 

203 """ 

204 Create a function from the strings name, signature, and body. 

205 evaldict is the evaluation dictionary. If addsource is true, an 

206 attribute __source__ is added to the result. The attributes attrs 

207 are added, if any. 

208 """ 

209 if isinstance(obj, str): # "name(signature)" 

210 name, rest = obj.strip().split('(', 1) 

211 signature = rest[:-1] # strip a right parens 

212 func = None 

213 else: # a function 

214 name = None 

215 signature = None 

216 func = obj 

217 self = cls(func, name, signature, defaults, doc, module) 

218 ibody = '\n'.join(' ' + line for line in body.splitlines()) 

219 return self.make('def %(name)s(%(signature)s):\n' + ibody, 

220 evaldict, addsource, **attrs) 

221 

222 

223def decorate(func, caller): 

224 """ 

225 decorate(func, caller) decorates a function using a caller. 

226 """ 

227 evaldict = func.__globals__.copy() 

228 evaldict['_call_'] = caller 

229 evaldict['_func_'] = func 

230 fun = FunctionMaker.create( 

231 func, "return _call_(_func_, %(shortsignature)s)", 

232 evaldict, __wrapped__=func) 

233 if hasattr(func, '__qualname__'): 

234 fun.__qualname__ = func.__qualname__ 

235 return fun 

236 

237 

238def decorator(caller, _func=None): 

239 """decorator(caller) converts a caller function into a decorator""" 

240 if _func is not None: # return a decorated function 

241 # this is obsolete behavior; you should use decorate instead 

242 return decorate(_func, caller) 

243 # else return a decorator function 

244 if inspect.isclass(caller): 

245 name = caller.__name__.lower() 

246 callerfunc = get_init(caller) 

247 doc = 'decorator(%s) converts functions/generators into ' \ 

248 'factories of %s objects' % (caller.__name__, caller.__name__) 

249 elif inspect.isfunction(caller): 

250 if caller.__name__ == '<lambda>': 

251 name = '_lambda_' 

252 else: 

253 name = caller.__name__ 

254 callerfunc = caller 

255 doc = caller.__doc__ 

256 else: # assume caller is an object with a __call__ method 

257 name = caller.__class__.__name__.lower() 

258 callerfunc = caller.__call__.__func__ 

259 doc = caller.__call__.__doc__ 

260 evaldict = callerfunc.__globals__.copy() 

261 evaldict['_call_'] = caller 

262 evaldict['_decorate_'] = decorate 

263 return FunctionMaker.create( 

264 '%s(func)' % name, 'return _decorate_(func, _call_)', 

265 evaldict, doc=doc, module=caller.__module__, 

266 __wrapped__=caller) 

267 

268 

269# ####################### contextmanager ####################### # 

270 

271try: # Python >= 3.2 

272 from contextlib import _GeneratorContextManager 

273except ImportError: # Python >= 2.5 

274 from contextlib import GeneratorContextManager as _GeneratorContextManager 

275 

276 

277class ContextManager(_GeneratorContextManager): 

278 def __call__(self, func): 

279 """Context manager decorator""" 

280 return FunctionMaker.create( 

281 func, "with _self_: return _func_(%(shortsignature)s)", 

282 dict(_self_=self, _func_=func), __wrapped__=func) 

283 

284 

285init = getfullargspec(_GeneratorContextManager.__init__) 

286n_args = len(init.args) 

287if n_args == 2 and not init.varargs: # (self, genobj) Python 2.7 

288 def __init__(self, g, *a, **k): 

289 return _GeneratorContextManager.__init__(self, g(*a, **k)) 

290 ContextManager.__init__ = __init__ 

291elif n_args == 2 and init.varargs: # (self, gen, *a, **k) Python 3.4 

292 pass 

293elif n_args == 4: # (self, gen, args, kwds) Python 3.5 

294 def __init__(self, g, *a, **k): 

295 return _GeneratorContextManager.__init__(self, g, a, k) 

296 ContextManager.__init__ = __init__ 

297 

298contextmanager = decorator(ContextManager) 

299 

300 

301# ############################ dispatch_on ############################ # 

302 

303def append(a, vancestors): 

304 """ 

305 Append ``a`` to the list of the virtual ancestors, unless it is already 

306 included. 

307 """ 

308 add = True 

309 for j, va in enumerate(vancestors): 

310 if issubclass(va, a): 

311 add = False 

312 break 

313 if issubclass(a, va): 

314 vancestors[j] = a 

315 add = False 

316 if add: 

317 vancestors.append(a) 

318 

319 

320# inspired from simplegeneric by P.J. Eby and functools.singledispatch 

321def dispatch_on(*dispatch_args): 

322 """ 

323 Factory of decorators turning a function into a generic function 

324 dispatching on the given arguments. 

325 """ 

326 assert dispatch_args, 'No dispatch args passed' 

327 dispatch_str = '(%s,)' % ', '.join(dispatch_args) 

328 

329 def check(arguments, wrong=operator.ne, msg=''): 

330 """Make sure one passes the expected number of arguments""" 

331 if wrong(len(arguments), len(dispatch_args)): 

332 raise TypeError('Expected %d arguments, got %d%s' % 

333 (len(dispatch_args), len(arguments), msg)) 

334 

335 def gen_func_dec(func): 

336 """Decorator turning a function into a generic function""" 

337 

338 # first check the dispatch arguments 

339 argset = set(getfullargspec(func).args) 

340 if not set(dispatch_args) <= argset: 

341 raise NameError('Unknown dispatch arguments %s' % dispatch_str) 

342 

343 typemap = {} 

344 

345 def vancestors(*types): 

346 """ 

347 Get a list of sets of virtual ancestors for the given types 

348 """ 

349 check(types) 

350 ras = [[] for _ in range(len(dispatch_args))] 

351 for types_ in typemap: 

352 for t, type_, ra in zip(types, types_, ras): 

353 if issubclass(t, type_) and type_ not in t.__mro__: 

354 append(type_, ra) 

355 return [set(ra) for ra in ras] 

356 

357 def ancestors(*types): 

358 """ 

359 Get a list of virtual MROs, one for each type 

360 """ 

361 check(types) 

362 lists = [] 

363 for t, vas in zip(types, vancestors(*types)): 

364 n_vas = len(vas) 

365 if n_vas > 1: 

366 raise RuntimeError( 

367 'Ambiguous dispatch for %s: %s' % (t, vas)) 

368 elif n_vas == 1: 

369 va, = vas 

370 mro = type('t', (t, va), {}).__mro__[1:] 

371 else: 

372 mro = t.__mro__ 

373 lists.append(mro[:-1]) # discard t and object 

374 return lists 

375 

376 def register(*types): 

377 """ 

378 Decorator to register an implementation for the given types 

379 """ 

380 check(types) 

381 

382 def dec(f): 

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

384 typemap[types] = f 

385 return f 

386 return dec 

387 

388 def dispatch_info(*types): 

389 """ 

390 An utility to introspect the dispatch algorithm 

391 """ 

392 check(types) 

393 lst = [tuple(a.__name__ for a in anc) 

394 for anc in itertools.product(*ancestors(*types))] 

395 return lst 

396 

397 def _dispatch(dispatch_args, *args, **kw): 

398 types = tuple(type(arg) for arg in dispatch_args) 

399 try: # fast path 

400 f = typemap[types] 

401 except KeyError: 

402 pass 

403 else: 

404 return f(*args, **kw) 

405 combinations = itertools.product(*ancestors(*types)) 

406 next(combinations) # the first one has been already tried 

407 for types_ in combinations: 

408 f = typemap.get(types_) 

409 if f is not None: 

410 return f(*args, **kw) 

411 

412 # else call the default implementation 

413 return func(*args, **kw) 

414 

415 return FunctionMaker.create( 

416 func, 'return _f_(%s, %%(shortsignature)s)' % dispatch_str, 

417 dict(_f_=_dispatch), register=register, default=func, 

418 typemap=typemap, vancestors=vancestors, ancestors=ancestors, 

419 dispatch_info=dispatch_info, __wrapped__=func) 

420 

421 gen_func_dec.__name__ = 'dispatch_on' + dispatch_str 

422 return gen_func_dec