1- # ######################### LICENCE ############################ #
1+ # ######################### LICENSE ############################ #
22
3- # Copyright (c) 2005-2015 , Michele Simionato
3+ # Copyright (c) 2005-2016 , Michele Simionato
44# All rights reserved.
55
66# Redistribution and use in source and binary forms, with or without
3131Decorator module, see http://pypi.python.org/pypi/decorator
3232for the documentation.
3333"""
34-
35- __version__ = '3.4.2'
36-
37- __all__ = ["decorator" , "FunctionMaker" , "contextmanager" ]
34+ from __future__ import print_function
3835
3936import re
4037import sys
4138import inspect
39+ import operator
40+ import itertools
41+ import collections
42+
43+ __version__ = '4.0.10'
4244
4345if sys .version >= '3' :
4446 from inspect import getfullargspec
@@ -60,9 +62,21 @@ def __iter__(self):
6062 yield self .varkw
6163 yield self .defaults
6264
65+ getargspec = inspect .getargspec
66+
6367 def get_init (cls ):
6468 return cls .__init__ .__func__
6569
70+ # getargspec has been deprecated in Python 3.5
71+ ArgSpec = collections .namedtuple (
72+ 'ArgSpec' , 'args varargs varkw defaults' )
73+
74+
75+ def getargspec (f ):
76+ """A replacement for inspect.getargspec"""
77+ spec = getfullargspec (f )
78+ return ArgSpec (spec .args , spec .varargs , spec .varkw , spec .defaults )
79+
6680DEF = re .compile ('\s*def\s*([_\w][_\w\d]*)\s*\(' )
6781
6882
@@ -73,6 +87,10 @@ class FunctionMaker(object):
7387 It has attributes name, doc, module, signature, defaults, dict and
7488 methods update and make.
7589 """
90+
91+ # Atomic get-and-increment provided by the GIL
92+ _compile_count = itertools .count ()
93+
7694 def __init__ (self , func = None , name = None , signature = None ,
7795 defaults = None , doc = None , module = None , funcdict = None ):
7896 self .shortsignature = signature
@@ -160,11 +178,16 @@ def make(self, src_templ, evaldict=None, addsource=False, **attrs):
160178 for n in names :
161179 if n in ('_func_' , '_call_' ):
162180 raise NameError ('%s is overridden in\n %s' % (n , src ))
163- if not src .endswith ('\n ' ): # add a newline just for safety
164- src += '\n ' # this is needed in old versions of Python
181+
182+ if not src .endswith ('\n ' ): # add a newline for old Pythons
183+ src += '\n '
184+
185+ # Ensure each generated function has a unique filename for profilers
186+ # (such as cProfile) that depend on the tuple of (<filename>,
187+ # <definition line>, <function name>) being unique.
188+ filename = '<decorator-gen-%d>' % (next (self ._compile_count ),)
165189 try :
166- code = compile (src , '<string>' , 'single' )
167- # print >> sys.stderr, 'Compiling %s' % src
190+ code = compile (src , filename , 'single' )
168191 exec (code , evaldict )
169192 except :
170193 print ('Error in generated code:' , file = sys .stderr )
@@ -199,75 +222,196 @@ def create(cls, obj, body, evaldict, defaults=None,
199222 evaldict , addsource , ** attrs )
200223
201224
202- def decorator ( caller , func = None ):
225+ def decorate ( func , caller ):
203226 """
204- decorator(caller) converts a caller function into a decorator;
205- decorator(caller, func) decorates a function using a caller.
227+ decorate(func, caller) decorates a function using a caller.
206228 """
207- if func is not None : # returns a decorated function
208- evaldict = func .__globals__ .copy ()
209- evaldict ['_call_' ] = caller
210- evaldict ['_func_' ] = func
211- return FunctionMaker .create (
212- func , "return _call_(_func_, %(shortsignature)s)" ,
213- evaldict , __wrapped__ = func )
214- else : # returns a decorator
215- if inspect .isclass (caller ):
216- name = caller .__name__ .lower ()
217- callerfunc = get_init (caller )
218- doc = 'decorator(%s) converts functions/generators into ' \
219- 'factories of %s objects' % (caller .__name__ , caller .__name__ )
220- fun = getfullargspec (callerfunc ).args [1 ] # second arg
221- elif inspect .isfunction (caller ):
222- if caller .__name__ == '<lambda>' :
223- name = '_lambda_'
224- else :
225- name = caller .__name__
226- callerfunc = caller
227- doc = caller .__doc__
228- fun = getfullargspec (callerfunc ).args [0 ] # first arg
229- else : # assume caller is an object with a __call__ method
230- name = caller .__class__ .__name__ .lower ()
231- callerfunc = caller .__call__ .__func__
232- doc = caller .__call__ .__doc__
233- fun = getfullargspec (callerfunc ).args [1 ] # second arg
234- evaldict = callerfunc .__globals__ .copy ()
235- evaldict ['_call_' ] = caller
236- evaldict ['decorator' ] = decorator
237- return FunctionMaker .create (
238- '%s(%s)' % (name , fun ),
239- 'return decorator(_call_, %s)' % fun ,
240- evaldict , call = caller , doc = doc , module = caller .__module__ )
241-
229+ evaldict = dict (_call_ = caller , _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
242236
243- # ####################### contextmanager ####################### #
244237
245- def __call__ (self , func ):
246- 'Context manager decorator'
238+ def 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+ doc = 'decorator(%s) converts functions/generators into ' \
247+ 'factories of %s objects' % (caller .__name__ , caller .__name__ )
248+ elif inspect .isfunction (caller ):
249+ if caller .__name__ == '<lambda>' :
250+ name = '_lambda_'
251+ else :
252+ name = caller .__name__
253+ doc = caller .__doc__
254+ else : # assume caller is an object with a __call__ method
255+ name = caller .__class__ .__name__ .lower ()
256+ doc = caller .__call__ .__doc__
257+ evaldict = dict (_call_ = caller , _decorate_ = decorate )
247258 return FunctionMaker .create (
248- func , "with _self_: return _func_(%(shortsignature)s)" ,
249- dict (_self_ = self , _func_ = func ), __wrapped__ = func )
259+ '%s(func)' % name , 'return _decorate_(func, _call_)' ,
260+ evaldict , doc = doc , module = caller .__module__ ,
261+ __wrapped__ = caller )
250262
251- try : # Python >= 3.2
252263
264+ # ####################### contextmanager ####################### #
265+
266+ try : # Python >= 3.2
253267 from contextlib import _GeneratorContextManager
268+ except ImportError : # Python >= 2.5
269+ from contextlib import GeneratorContextManager as _GeneratorContextManager
254270
255- class ContextManager (_GeneratorContextManager ):
256- __call__ = __call__
257271
258- except ImportError : # Python >= 2.5
272+ class ContextManager (_GeneratorContextManager ):
273+ def __call__ (self , func ):
274+ """Context manager decorator"""
275+ return FunctionMaker .create (
276+ func , "with _self_: return _func_(%(shortsignature)s)" ,
277+ dict (_self_ = self , _func_ = func ), __wrapped__ = func )
259278
260- try :
261- from contextlib import GeneratorContextManager
262- except ImportError : # Python 2.4
263- class ContextManager ( object ):
264- def __init__ (self , g , * a , ** k ):
265- raise RuntimeError (
266- 'You cannot used contextmanager in Python 2.4!' )
267- else :
268- class ContextManager ( GeneratorContextManager ):
269- def __init__ (self , g , * a , ** k ):
270- return GeneratorContextManager .__init__ (self , g ( * a , ** k ) )
271- __call__ = __call__
279+ init = getfullargspec ( _GeneratorContextManager . __init__ )
280+ n_args = len ( init . args )
281+ if n_args == 2 and not init . varargs : # (self, genobj) Python 2.7
282+ def __init__ ( self , g , * a , ** k ):
283+ return _GeneratorContextManager . __init__ (self , g ( * a , ** k ))
284+ ContextManager . __init__ = __init__
285+ elif n_args == 2 and init . varargs : # (self, gen, *a, **k) Python 3.4
286+ pass
287+ elif n_args == 4 : # (self, gen, args, kwds) Python 3.5
288+ def __init__ (self , g , * a , ** k ):
289+ return _GeneratorContextManager .__init__ (self , g , a , k )
290+ ContextManager . __init__ = __init__
272291
273292contextmanager = decorator (ContextManager )
293+
294+
295+ # ############################ dispatch_on ############################ #
296+
297+ def append (a , vancestors ):
298+ """
299+ Append ``a`` to the list of the virtual ancestors, unless it is already
300+ included.
301+ """
302+ add = True
303+ for j , va in enumerate (vancestors ):
304+ if issubclass (va , a ):
305+ add = False
306+ break
307+ if issubclass (a , va ):
308+ vancestors [j ] = a
309+ add = False
310+ if add :
311+ vancestors .append (a )
312+
313+
314+ # inspired from simplegeneric by P.J. Eby and functools.singledispatch
315+ def dispatch_on (* dispatch_args ):
316+ """
317+ Factory of decorators turning a function into a generic function
318+ dispatching on the given arguments.
319+ """
320+ assert dispatch_args , 'No dispatch args passed'
321+ dispatch_str = '(%s,)' % ', ' .join (dispatch_args )
322+
323+ def check (arguments , wrong = operator .ne , msg = '' ):
324+ """Make sure one passes the expected number of arguments"""
325+ if wrong (len (arguments ), len (dispatch_args )):
326+ raise TypeError ('Expected %d arguments, got %d%s' %
327+ (len (dispatch_args ), len (arguments ), msg ))
328+
329+ def gen_func_dec (func ):
330+ """Decorator turning a function into a generic function"""
331+
332+ # first check the dispatch arguments
333+ argset = set (getfullargspec (func ).args )
334+ if not set (dispatch_args ) <= argset :
335+ raise NameError ('Unknown dispatch arguments %s' % dispatch_str )
336+
337+ typemap = {}
338+
339+ def vancestors (* types ):
340+ """
341+ Get a list of sets of virtual ancestors for the given types
342+ """
343+ check (types )
344+ ras = [[] for _ in range (len (dispatch_args ))]
345+ for types_ in typemap :
346+ for t , type_ , ra in zip (types , types_ , ras ):
347+ if issubclass (t , type_ ) and type_ not in t .__mro__ :
348+ append (type_ , ra )
349+ return [set (ra ) for ra in ras ]
350+
351+ def ancestors (* types ):
352+ """
353+ Get a list of virtual MROs, one for each type
354+ """
355+ check (types )
356+ lists = []
357+ for t , vas in zip (types , vancestors (* types )):
358+ n_vas = len (vas )
359+ if n_vas > 1 :
360+ raise RuntimeError (
361+ 'Ambiguous dispatch for %s: %s' % (t , vas ))
362+ elif n_vas == 1 :
363+ va , = vas
364+ mro = type ('t' , (t , va ), {}).__mro__ [1 :]
365+ else :
366+ mro = t .__mro__
367+ lists .append (mro [:- 1 ]) # discard t and object
368+ return lists
369+
370+ def register (* types ):
371+ """
372+ Decorator to register an implementation for the given types
373+ """
374+ check (types )
375+
376+ def dec (f ):
377+ check (getfullargspec (f ).args , operator .lt , ' in ' + f .__name__ )
378+ typemap [types ] = f
379+ return f
380+ return dec
381+
382+ def dispatch_info (* types ):
383+ """
384+ An utility to introspect the dispatch algorithm
385+ """
386+ check (types )
387+ lst = []
388+ for anc in itertools .product (* ancestors (* types )):
389+ lst .append (tuple (a .__name__ for a in anc ))
390+ return lst
391+
392+ def _dispatch (dispatch_args , * args , ** kw ):
393+ types = tuple (type (arg ) for arg in dispatch_args )
394+ try : # fast path
395+ f = typemap [types ]
396+ except KeyError :
397+ pass
398+ else :
399+ return f (* args , ** kw )
400+ combinations = itertools .product (* ancestors (* types ))
401+ next (combinations ) # the first one has been already tried
402+ for types_ in combinations :
403+ f = typemap .get (types_ )
404+ if f is not None :
405+ return f (* args , ** kw )
406+
407+ # else call the default implementation
408+ return func (* args , ** kw )
409+
410+ return FunctionMaker .create (
411+ func , 'return _f_(%s, %%(shortsignature)s)' % dispatch_str ,
412+ dict (_f_ = _dispatch ), register = register , default = func ,
413+ typemap = typemap , vancestors = vancestors , ancestors = ancestors ,
414+ dispatch_info = dispatch_info , __wrapped__ = func )
415+
416+ gen_func_dec .__name__ = 'dispatch_on' + dispatch_str
417+ return gen_func_dec
0 commit comments