diff options
author | nkozlovskiy <nmk@ydb.tech> | 2023-09-29 12:24:06 +0300 |
---|---|---|
committer | nkozlovskiy <nmk@ydb.tech> | 2023-09-29 12:41:34 +0300 |
commit | e0e3e1717e3d33762ce61950504f9637a6e669ed (patch) | |
tree | bca3ff6939b10ed60c3d5c12439963a1146b9711 /contrib/python/decorator | |
parent | 38f2c5852db84c7b4d83adfcb009eb61541d1ccd (diff) | |
download | ydb-e0e3e1717e3d33762ce61950504f9637a6e669ed.tar.gz |
add ydb deps
Diffstat (limited to 'contrib/python/decorator')
21 files changed, 6282 insertions, 0 deletions
diff --git a/contrib/python/decorator/py2/.dist-info/METADATA b/contrib/python/decorator/py2/.dist-info/METADATA new file mode 100644 index 0000000000..fd12277a01 --- /dev/null +++ b/contrib/python/decorator/py2/.dist-info/METADATA @@ -0,0 +1,131 @@ +Metadata-Version: 2.1 +Name: decorator +Version: 4.4.2 +Summary: Decorators for Humans +Home-page: https://github.com/micheles/decorator +Author: Michele Simionato +Author-email: michele.simionato@gmail.com +License: new BSD License +Keywords: decorators generic utility +Platform: All +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Natural Language :: English +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.2 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Utilities +Requires-Python: >=2.6, !=3.0.*, !=3.1.* + +Decorators for Humans +===================== + +The goal of the decorator module is to make it easy to define +signature-preserving function decorators and decorator factories. +It also includes an implementation of multiple dispatch and other niceties +(please check the docs). It is released under a two-clauses +BSD license, i.e. basically you can do whatever you want with it but I am not +responsible. + +Installation +------------- + +If you are lazy, just perform + + ``$ pip install decorator`` + +which will install just the module on your system. + +If you prefer to install the full distribution from source, including +the documentation, clone the `GitHub repo`_ or download the tarball_, unpack it and run + + ``$ pip install .`` + +in the main directory, possibly as superuser. + +.. _tarball: https://pypi.org/project/decorator/#files +.. _GitHub repo: https://github.com/micheles/decorator + +Testing +-------- + +If you have the source code installation you can run the tests with + + `$ python src/tests/test.py -v` + +or (if you have setuptools installed) + + `$ python setup.py test` + +Notice that you may run into trouble if in your system there +is an older version of the decorator module; in such a case remove the +old version. It is safe even to copy the module `decorator.py` over +an existing one, since we kept backward-compatibility for a long time. + +Repository +--------------- + +The project is hosted on GitHub. You can look at the source here: + + https://github.com/micheles/decorator + +Documentation +--------------- + +The documentation has been moved to https://github.com/micheles/decorator/blob/master/docs/documentation.md + +From there you can get a PDF version by simply using the print +functionality of your browser. + +Here is the documentation for previous versions of the module: + +https://github.com/micheles/decorator/blob/4.3.2/docs/tests.documentation.rst +https://github.com/micheles/decorator/blob/4.2.1/docs/tests.documentation.rst +https://github.com/micheles/decorator/blob/4.1.2/docs/tests.documentation.rst +https://github.com/micheles/decorator/blob/4.0.0/documentation.rst +https://github.com/micheles/decorator/blob/3.4.2/documentation.rst + +For the impatient +----------------- + +Here is an example of how to define a family of decorators tracing slow +operations: + +.. code-block:: python + + from decorator import decorator + + @decorator + def warn_slow(func, timelimit=60, *args, **kw): + t0 = time.time() + result = func(*args, **kw) + dt = time.time() - t0 + if dt > timelimit: + logging.warn('%s took %d seconds', func.__name__, dt) + else: + logging.info('%s took %d seconds', func.__name__, dt) + return result + + @warn_slow # warn if it takes more than 1 minute + def preprocess_input_files(inputdir, tempdir): + ... + + @warn_slow(timelimit=600) # warn if it takes more than 10 minutes + def run_calculation(tempdir, outdir): + ... + +Enjoy! + + diff --git a/contrib/python/decorator/py2/.dist-info/top_level.txt b/contrib/python/decorator/py2/.dist-info/top_level.txt new file mode 100644 index 0000000000..3fe18a4d1c --- /dev/null +++ b/contrib/python/decorator/py2/.dist-info/top_level.txt @@ -0,0 +1 @@ +decorator diff --git a/contrib/python/decorator/py2/LICENSE.txt b/contrib/python/decorator/py2/LICENSE.txt new file mode 100644 index 0000000000..b0ade0487e --- /dev/null +++ b/contrib/python/decorator/py2/LICENSE.txt @@ -0,0 +1,26 @@ +Copyright (c) 2005-2018, 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. diff --git a/contrib/python/decorator/py2/README.rst b/contrib/python/decorator/py2/README.rst new file mode 100644 index 0000000000..f56161c74a --- /dev/null +++ b/contrib/python/decorator/py2/README.rst @@ -0,0 +1,98 @@ +Decorators for Humans +===================== + +The goal of the decorator module is to make it easy to define +signature-preserving function decorators and decorator factories. +It also includes an implementation of multiple dispatch and other niceties +(please check the docs). It is released under a two-clauses +BSD license, i.e. basically you can do whatever you want with it but I am not +responsible. + +Installation +------------- + +If you are lazy, just perform + + ``$ pip install decorator`` + +which will install just the module on your system. + +If you prefer to install the full distribution from source, including +the documentation, clone the `GitHub repo`_ or download the tarball_, unpack it and run + + ``$ pip install .`` + +in the main directory, possibly as superuser. + +.. _tarball: https://pypi.org/project/decorator/#files +.. _GitHub repo: https://github.com/micheles/decorator + +Testing +-------- + +If you have the source code installation you can run the tests with + + `$ python src/tests/test.py -v` + +or (if you have setuptools installed) + + `$ python setup.py test` + +Notice that you may run into trouble if in your system there +is an older version of the decorator module; in such a case remove the +old version. It is safe even to copy the module `decorator.py` over +an existing one, since we kept backward-compatibility for a long time. + +Repository +--------------- + +The project is hosted on GitHub. You can look at the source here: + + https://github.com/micheles/decorator + +Documentation +--------------- + +The documentation has been moved to https://github.com/micheles/decorator/blob/master/docs/documentation.md + +From there you can get a PDF version by simply using the print +functionality of your browser. + +Here is the documentation for previous versions of the module: + +https://github.com/micheles/decorator/blob/4.3.2/docs/tests.documentation.rst +https://github.com/micheles/decorator/blob/4.2.1/docs/tests.documentation.rst +https://github.com/micheles/decorator/blob/4.1.2/docs/tests.documentation.rst +https://github.com/micheles/decorator/blob/4.0.0/documentation.rst +https://github.com/micheles/decorator/blob/3.4.2/documentation.rst + +For the impatient +----------------- + +Here is an example of how to define a family of decorators tracing slow +operations: + +.. code-block:: python + + from decorator import decorator + + @decorator + def warn_slow(func, timelimit=60, *args, **kw): + t0 = time.time() + result = func(*args, **kw) + dt = time.time() - t0 + if dt > timelimit: + logging.warn('%s took %d seconds', func.__name__, dt) + else: + logging.info('%s took %d seconds', func.__name__, dt) + return result + + @warn_slow # warn if it takes more than 1 minute + def preprocess_input_files(inputdir, tempdir): + ... + + @warn_slow(timelimit=600) # warn if it takes more than 10 minutes + def run_calculation(tempdir, outdir): + ... + +Enjoy! diff --git a/contrib/python/decorator/py2/decorator.py b/contrib/python/decorator/py2/decorator.py new file mode 100644 index 0000000000..b1f8b567e9 --- /dev/null +++ b/contrib/python/decorator/py2/decorator.py @@ -0,0 +1,454 @@ +# ######################### LICENSE ############################ # + +# Copyright (c) 2005-2018, 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 + +import re +import sys +import inspect +import operator +import itertools +import collections + +__version__ = '4.4.2' + +if sys.version_info >= (3,): + from inspect import getfullargspec + + def get_init(cls): + return cls.__init__ +else: + FullArgSpec = collections.namedtuple( + 'FullArgSpec', 'args varargs varkw defaults ' + 'kwonlyargs kwonlydefaults annotations') + + def getfullargspec(f): + "A quick and dirty replacement for getfullargspec for Python 2.X" + return FullArgSpec._make(inspect.getargspec(f) + ([], None, {})) + + def get_init(cls): + return cls.__init__.__func__ + +try: + iscoroutinefunction = inspect.iscoroutinefunction +except AttributeError: + # let's assume there are no coroutine functions in old Python + def iscoroutinefunction(f): + return False +try: + from inspect import isgeneratorfunction +except ImportError: + # assume no generator function in old Python versions + def isgeneratorfunction(caller): + return False + + +DEF = re.compile(r'\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() + + # make pylint happy + args = varargs = varkw = defaults = kwonlyargs = kwonlydefaults = () + + def __init__(self, func=None, name=None, signature=None, + defaults=None, doc=None, module=None, funcdict=None): + self.shortsignature = signature + if func: + # func can be a class or a callable, but not an instance method + self.name = func.__name__ + if self.name == '<lambda>': # small hack for lambda functions + self.name = '_lambda_' + self.doc = func.__doc__ + 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'): + setattr(self, a, getattr(argspec, a)) + for i, arg in enumerate(self.args): + setattr(self, 'arg%d' % i, arg) + 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: + self.name = name + if signature is not None: + self.signature = signature + if defaults: + self.defaults = defaults + if doc: + self.doc = doc + if module: + self.module = module + if funcdict: + self.dict = funcdict + # check existence required attributes + assert hasattr(self, 'name') + if not hasattr(self, 'signature'): + raise TypeError('You are decorating a non function: %s' % func) + + def update(self, func, **kw): + "Update the signature of func with the data in self" + func.__name__ = self.name + func.__doc__ = getattr(self, 'doc', None) + func.__dict__ = getattr(self, 'dict', {}) + func.__defaults__ = self.defaults + func.__kwdefaults__ = self.kwonlydefaults or None + 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) + + def make(self, src_templ, evaldict=None, addsource=False, **attrs): + "Make a new function from a given template and update the signature" + src = src_templ % vars(self) # expand name and signature + evaldict = evaldict or {} + mo = DEF.search(src) + if mo is None: + 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(',')]) + 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 for old Pythons + src += '\n' + + # Ensure each generated function has a unique filename for profilers + # (such as cProfile) that depend on the tuple of (<filename>, + # <definition line>, <function name>) being unique. + filename = '<decorator-gen-%d>' % next(self._compile_count) + try: + code = compile(src, filename, 'single') + exec(code, evaldict) + except Exception: + print('Error in generated code:', file=sys.stderr) + print(src, file=sys.stderr) + raise + func = evaldict[name] + if addsource: + attrs['__source__'] = src + self.update(func, **attrs) + return func + + @classmethod + def create(cls, obj, body, evaldict, defaults=None, + 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. + """ + if isinstance(obj, str): # "name(signature)" + name, rest = obj.strip().split('(', 1) + signature = rest[:-1] # strip a right parens + func = None + else: # a function + name = None + signature = None + func = obj + self = cls(func, name, signature, defaults, doc, module) + ibody = '\n'.join(' ' + line for line in body.splitlines()) + caller = evaldict.get('_call_') # when called from `decorate` + if caller and iscoroutinefunction(caller): + body = ('async def %(name)s(%(signature)s):\n' + ibody).replace( + 'return', 'return await') + else: + body = 'def %(name)s(%(signature)s):\n' + ibody + return self.make(body, evaldict, addsource, **attrs) + + +def decorate(func, caller, extras=()): + """ + decorate(func, caller) decorates a function using a caller. + If the caller is a generator function, the resulting function + will be a generator function. + """ + evaldict = dict(_call_=caller, _func_=func) + es = '' + for i, extra in enumerate(extras): + ex = '_e%d_' % i + evaldict[ex] = extra + es += ex + ', ' + + if '3.5' <= sys.version < '3.6': + # with Python 3.5 isgeneratorfunction returns True for all coroutines + # however we know that it is NOT possible to have a generator + # coroutine in python 3.5: PEP525 was not there yet + generatorcaller = isgeneratorfunction( + caller) and not iscoroutinefunction(caller) + else: + generatorcaller = isgeneratorfunction(caller) + if generatorcaller: + fun = FunctionMaker.create( + func, "for res in _call_(_func_, %s%%(shortsignature)s):\n" + " yield res" % es, evaldict, __wrapped__=func) + else: + fun = FunctionMaker.create( + func, "return _call_(_func_, %s%%(shortsignature)s)" % es, + 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 + defaultargs, defaults = '', () + if inspect.isclass(caller): + name = caller.__name__.lower() + doc = 'decorator(%s) converts functions/generators into ' \ + 'factories of %s objects' % (caller.__name__, caller.__name__) + elif inspect.isfunction(caller): + if caller.__name__ == '<lambda>': + name = '_lambda_' + else: + name = caller.__name__ + doc = caller.__doc__ + nargs = caller.__code__.co_argcount + ndefs = len(caller.__defaults__ or ()) + defaultargs = ', '.join(caller.__code__.co_varnames[nargs-ndefs:nargs]) + if defaultargs: + defaultargs += ',' + defaults = caller.__defaults__ + else: # assume caller is an object with a __call__ method + name = caller.__class__.__name__.lower() + doc = caller.__call__.__doc__ + evaldict = dict(_call=caller, _decorate_=decorate) + dec = FunctionMaker.create( + '%s(func, %s)' % (name, defaultargs), + 'if func is None: return lambda func: _decorate_(func, _call, (%s))\n' + 'return _decorate_(func, _call, (%s))' % (defaultargs, defaultargs), + evaldict, doc=doc, module=caller.__module__, __wrapped__=caller) + if defaults: + dec.__defaults__ = (None,) + defaults + return dec + + +# ####################### 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, "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) + + +def contextmanager(func): + # Enable Pylint config: contextmanager-decorators=decorator.contextmanager + return _contextmanager(func) + + +# ############################ 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( + 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 diff --git a/contrib/python/decorator/py2/tests/__init__.py b/contrib/python/decorator/py2/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/contrib/python/decorator/py2/tests/__init__.py diff --git a/contrib/python/decorator/py2/tests/documentation.py b/contrib/python/decorator/py2/tests/documentation.py new file mode 100644 index 0000000000..46a932aa77 --- /dev/null +++ b/contrib/python/decorator/py2/tests/documentation.py @@ -0,0 +1,1897 @@ +from __future__ import print_function +import sys +import threading +import time +import functools +import itertools +import collections +try: + import collections.abc as c +except ImportError: + c = collections + collections.abc = collections +from decorator import (decorator, decorate, FunctionMaker, contextmanager, + dispatch_on, __version__) + +doc = r"""Decorators for Humans +---------------------------------- + +|Author | Michele Simionato| +|---|---| +|E-mail | michele.simionato@gmail.com| +|Version| $VERSION ($DATE)| +|Supports| Python 2.6, 2.7, 3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8| +|Download page| http://pypi.python.org/pypi/decorator/$VERSION| +|Installation| ``pip install decorator``| +|License | BSD license| + +Introduction +----------------------------------------- + +The ``decorator`` module is over ten years old, but still alive and +kicking. It is used by several frameworks (IPython, scipy, authkit, +pylons, pycuda, sugar, ...) and has been stable for a *long* +time. It is your best option if you want to preserve the signature of +decorated functions in a consistent way across Python +releases. Version 4 is fully compatible with the past, except for +one thing: support for Python 2.4 and 2.5 has been dropped. That +decision made it possible to use a single code base both for Python +2.X and Python 3.X. This is a *huge* bonus, since I could remove over +2,000 lines of duplicated documentation/doctests. Having to maintain +separate docs for Python 2 and Python 3 effectively stopped any +development on the module for several years. Moreover, it is now +trivial to distribute the module as an universal + [wheel](http://pythonwheels.com) since 2to3 is no more +required. Since Python 2.5 has been released ages ago (in 2006), I felt that +it was reasonable to drop the support for it. If you need to support +ancient versions of Python, stick with the decorator module version +3.4.2. The current version supports all Python releases from 2.6 up. + +What's New in version 4 +----------------------- + +- **New documentation** + There is now a single manual for all Python versions, so I took the + opportunity to overhaul the documentation and to move it to readthedocs.org. + Even if you are a long-time user, you may want to revisit the docs, since + several examples have been improved. + +- **Packaging improvements** + The code is now also available in wheel format. Integration with + setuptools has improved and you can run the tests with the command + ``python setup.py test`` too. + +- **Code changes** + A new utility function ``decorate(func, caller)`` has been added. + It does the same job that was performed by the older + ``decorator(caller, func)``. The old functionality is now deprecated + and no longer documented, but still available for now. + +- **Multiple dispatch** + The decorator module now includes an implementation of generic + functions (sometimes called "multiple dispatch functions"). + The API is designed to mimic ``functools.singledispatch`` (added + in Python 3.4), but the implementation is much simpler. + Moreover, all decorators involved preserve the signature of the + decorated functions. For now, this exists mostly to demonstrate + the power of the module. In the future it could be enhanced/optimized. + In any case, it is very short and compact (less then 100 lines), so you + can extract it for your own use. Take it as food for thought. + +- **Python 3.5 coroutines** + From version 4.1 it is possible to decorate coroutines, i.e. functions + defined with the `async def` syntax, and to maintain the + `inspect.iscoroutinefunction` check working for the decorated function. + +- **Decorator factories** + From version 4.2 there is facility to define factories of decorators in + a simple way, a feature requested by the users since a long time. + +Usefulness of decorators +------------------------------------------------ + +Python decorators are an interesting example of why syntactic sugar +matters. In principle, their introduction in Python 2.4 changed +nothing, since they did not provide any new functionality which was not +already present in the language. In practice, their introduction has +significantly changed the way we structure our programs in Python. I +believe the change is for the best, and that decorators are a great +idea since: + +* decorators help reducing boilerplate code; +* decorators help separation of concerns; +* decorators enhance readability and maintenability; +* decorators are explicit. + +Still, as of now, writing custom decorators correctly requires +some experience and it is not as easy as it could be. For instance, +typical implementations of decorators involve nested functions, and +we all know that flat is better than nested. + +The aim of the ``decorator`` module it to simplify the usage of +decorators for the average programmer, and to popularize decorators by +showing various non-trivial examples. Of course, as all techniques, +decorators can be abused (I have seen that) and you should not try to +solve every problem with a decorator, just because you can. + +You may find the source code for all the examples +discussed here in the ``documentation.py`` file, which contains +the documentation you are reading in the form of doctests. + +Definitions +------------------------------------ + +Technically speaking, any Python object which can be called with one argument +can be used as a decorator. However, this definition is somewhat too large +to be really useful. It is more convenient to split the generic class of +decorators in two subclasses: + +1. **signature-preserving decorators**, callable objects which accept + a function as input and return a function as output, *with the + same signature* + +2. **signature-changing** decorators, i.e. decorators + which change the signature of their input function, or decorators + that return non-callable objects + +Signature-changing decorators have their use: for instance, the +builtin classes ``staticmethod`` and ``classmethod`` are in this +group. They take functions and return descriptor objects which +are neither functions, nor callables. + +Still, signature-preserving decorators are more common, and easier +to reason about. In particular, they can be composed together, +whereas other decorators generally cannot. + +Writing signature-preserving decorators from scratch is not that +obvious, especially if one wants to define proper decorators that +can accept functions with any signature. A simple example will clarify +the issue. + +Statement of the problem +------------------------------ + +A very common use case for decorators is the memoization of functions. +A ``memoize`` decorator works by caching +the result of the function call in a dictionary, so that the next time +the function is called with the same input parameters the result is retrieved +from the cache and not recomputed. + +There are many implementations of ``memoize`` in +http://www.python.org/moin/PythonDecoratorLibrary, +but they do not preserve the signature. In recent versions of +Python you can find a sophisticated ``lru_cache`` decorator +in the standard library's ``functools``. Here I am just +interested in giving an example. + +Consider the following simple implementation (note that it is +generally impossible to *correctly* memoize something +that depends on non-hashable arguments): + +$$memoize_uw + +Here I used the functools.update_wrapper_ utility, which was added +in Python 2.5 to simplify the writing of decorators. +(Previously, you needed to manually copy the function attributes +``__name__``, ``__doc__``, ``__module__``, and ``__dict__`` +to the decorated function by hand). + +Here is an example of usage: + +$$f1 + +This works insofar as the decorator accepts functions with generic signatures. +Unfortunately, it is *not* a signature-preserving decorator, since +``memoize_uw`` generally returns a function with a *different signature* +from the original. + +Consider for instance the following case: + +$$f1 + +Here, the original function takes a single argument named ``x``, +but the decorated function takes any number of arguments and +keyword arguments: + +```python +>>> from decorator import getfullargspec +>>> print(getfullargspec(f1)) +FullArgSpec(args=[], varargs='args', varkw='kw', defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={}) + +``` + +This means that introspection tools (like ``pydoc``) will give false +information about the signature of ``f1`` -- unless you are using +Python 3.5. This is pretty bad: ``pydoc`` will tell you that the +function accepts the generic signature ``*args, **kw``, but +calling the function with more than one argument raises an error: + +```python +>>> f1(0, 1) # doctest: +IGNORE_EXCEPTION_DETAIL +Traceback (most recent call last): + ... +TypeError: f1() takes exactly 1 positional argument (2 given) + +``` + +Notice that ``inspect.getfullargspec`` +will give the wrong signature, even in the latest Python, i.e. version 3.6 +at the time of writing. + +The solution +----------------------------------------- + +The solution is to provide a generic factory of generators, which +hides the complexity of making signature-preserving decorators +from the application programmer. The ``decorate`` function in +the ``decorator`` module is such a factory: + +```python +>>> from decorator import decorate + +``` + +``decorate`` takes two arguments: + +1. a caller function describing the functionality of the decorator, and + +2. a function to be decorated. + +The caller function must have signature ``(f, *args, **kw)``, and it +must call the original function ``f`` with arguments ``args`` and ``kw``, +implementing the wanted capability (in this case, memoization): + +$$_memoize + +Now, you can define your decorator as follows: + +$$memoize + +The difference from the nested function approach of ``memoize_uw`` +is that the decorator module forces you to lift the inner function +to the outer level. Moreover, you are forced to explicitly pass the +function you want to decorate; there are no closures. + +Here is a test of usage: + +```python +>>> @memoize +... def heavy_computation(): +... time.sleep(2) +... return "done" + +>>> print(heavy_computation()) # the first time it will take 2 seconds +done + +>>> print(heavy_computation()) # the second time it will be instantaneous +done + +``` + +The signature of ``heavy_computation`` is the one you would expect: + +```python +>>> print(getfullargspec(heavy_computation)) +FullArgSpec(args=[], varargs=None, varkw=None, defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={}) + +``` + +A ``trace`` decorator +------------------------------------------------------ + +Here is an example of how to define a simple ``trace`` decorator, +which prints a message whenever the traced function is called: + +$$_trace + +$$trace + +Here is an example of usage: + +```python +>>> @trace +... def f1(x): +... pass + +``` + +It is immediate to verify that ``f1`` works... + +```python +>>> f1(0) +calling f1 with args (0,), {} + +``` + +...and it that it has the correct signature: + +```python +>>> print(getfullargspec(f1)) +FullArgSpec(args=['x'], varargs=None, varkw=None, defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={}) + +``` + +The decorator works with functions of any signature: + +```python +>>> @trace +... def f(x, y=1, z=2, *args, **kw): +... pass + +>>> f(0, 3) +calling f with args (0, 3, 2), {} + +>>> print(getfullargspec(f)) +FullArgSpec(args=['x', 'y', 'z'], varargs='args', varkw='kw', defaults=(1, 2), kwonlyargs=[], kwonlydefaults=None, annotations={}) + +``` +$FUNCTION_ANNOTATIONS + +``decorator.decorator`` +--------------------------------------------- + +It can become tedious to write a caller function (like the above +``_trace`` example) and then a trivial wrapper +(``def trace(f): return decorate(f, _trace)``) every time. +Not to worry! The ``decorator`` module provides an easy shortcut +to convert the caller function into a signature-preserving decorator. + +It is the ``decorator`` function: + +```python +>>> from decorator import decorator +>>> print(decorator.__doc__) +decorator(caller) converts a caller function into a decorator + +``` +The ``decorator`` function can be used as a signature-changing +decorator, just like ``classmethod`` and ``staticmethod``. +But ``classmethod`` and ``staticmethod`` return generic +objects which are not callable. Instead, ``decorator`` returns +signature-preserving decorators (i.e. functions with a single argument). + +For instance, you can write: + +```python +>>> @decorator +... def trace(f, *args, **kw): +... kwstr = ', '.join('%r: %r' % (k, kw[k]) for k in sorted(kw)) +... print("calling %s with args %s, {%s}" % (f.__name__, args, kwstr)) +... return f(*args, **kw) + +``` + +And ``trace`` is now a decorator! + +```python +>>> trace # doctest: +ELLIPSIS +<function trace at 0x...> + +``` + +Here is an example of usage: + +```python +>>> @trace +... def func(): pass + +>>> func() +calling func with args (), {} + +``` + +The `decorator` function can also be used to define factories of decorators, +i.e. functions returning decorators. In general you can just write something +like this: + +```python +def decfactory(param1, param2, ...): + def caller(f, *args, **kw): + return somefunc(f, param1, param2, .., *args, **kw) + return decorator(caller) +``` + +This is fully general but requires an additional level of nesting. For this +reason since version 4.2 there is a facility to build +decorator factories by using a single caller with default arguments i.e. +writing something like this: + +```python +def caller(f, param1=default1, param2=default2, ..., *args, **kw): + return somefunc(f, param1, param2, *args, **kw) +decfactory = decorator(caller) +``` + +Notice that this simplified approach *only works with default arguments*, +i.e. `param1`, `param2` etc must have known defaults. Thanks to this +restriction, there exists an unique default decorator, i.e. the member +of the family which uses the default values for all parameters. Such +decorator can be written as ``decfactory()`` with no parameters specified; +moreover, as a shortcut, it is also possible to elide the parenthesis, +a feature much requested by the users. For years I have been opposite +to this feature request, since having explicit parenthesis to me is more clear +and less magic; however once this feature entered in decorators of +the Python standard library (I am referring to the [dataclass decorator]( +https://www.python.org/dev/peps/pep-0557/)) I finally gave up. + +The example below will show how it works in practice. + +Decorator factories +------------------------------------------- + +Sometimes one has to deal with blocking resources, such as ``stdin``. +Sometimes it is better to receive a "busy" message than just blocking +everything. +This can be accomplished with a suitable family of decorators (decorator +factory), parameterize by a string, the busy message: + +$$blocking + +Functions decorated with ``blocking`` will return a busy message if +the resource is unavailable, and the intended result if the resource is +available. For instance: + +```python +>>> @blocking(msg="Please wait ...") +... def read_data(): +... time.sleep(3) # simulate a blocking resource +... return "some data" + +>>> print(read_data()) # data is not available yet +Please wait ... + +>>> time.sleep(1) +>>> print(read_data()) # data is not available yet +Please wait ... + +>>> time.sleep(1) +>>> print(read_data()) # data is not available yet +Please wait ... + +>>> time.sleep(1.1) # after 3.1 seconds, data is available +>>> print(read_data()) +some data + +``` + +Decorator factories are most useful to framework builders. Here is an example +that gives an idea of how you could manage permissions in a framework: + +$$Action + +where ``restricted`` is a decorator factory defined as follows + +$$restricted + +Notice that if you forget to use the keyword argument notation, i.e. if you +write ``restricted(User)`` instead of ``restricted(user_class=User)`` you +will get an error + +```python +TypeError: You are decorating a non function: <class '__main__.User'> + +``` + +Be careful! + +``decorator(cls)`` +-------------------------------------------- + +The ``decorator`` facility can also produce a decorator starting +from a class with the signature of a caller. In such a case the +produced generator is able to convert functions into factories +to create instances of that class. + +As an example, here is a decorator which can convert a +blocking function into an asynchronous function. When +the function is called, it is executed in a separate thread. + +(This is similar to the approach used in the ``concurrent.futures`` package. +But I don't recommend that you implement futures this way; this is just an +example.) + +$$Future + +The decorated function returns a ``Future`` object. It has a ``.result()`` +method which blocks until the underlying thread finishes and returns +the final result. + +Here is the minimalistic usage: + +```python +>>> @decorator(Future) +... def long_running(x): +... time.sleep(.5) +... return x + +>>> fut1 = long_running(1) +>>> fut2 = long_running(2) +>>> fut1.result() + fut2.result() +3 + +``` + +contextmanager +------------------------------------- + +Python's standard library has the ``contextmanager`` decorator, +which converts a generator function into a ``GeneratorContextManager`` +factory. For instance, if you write this... + +```python +>>> from contextlib import contextmanager +>>> @contextmanager +... def before_after(before, after): +... print(before) +... yield +... print(after) + +``` + +...then ``before_after`` is a factory function that returns +``GeneratorContextManager`` objects, which provide the +use of the ``with`` statement: + +```python +>>> with before_after('BEFORE', 'AFTER'): +... print('hello') +BEFORE +hello +AFTER + +``` + +Basically, it is as if the content of the ``with`` block was executed +in the place of the ``yield`` expression in the generator function. + +In Python 3.2, ``GeneratorContextManager`` objects were enhanced with +a ``__call__`` method, so that they can be used as decorators, like so: + +```python +>>> ba = before_after('BEFORE', 'AFTER') +>>> +>>> @ba # doctest: +SKIP +... def hello(): +... print('hello') +... +>>> hello() # doctest: +SKIP +BEFORE +hello +AFTER + +``` + +The ``ba`` decorator basically inserts a ``with ba:`` block +inside the function. + +However, there are two issues: + +1. ``GeneratorContextManager`` objects are only callable in Python 3.2, + so the previous example breaks in older versions of Python. + (You can solve this by installing ``contextlib2``, which backports + the Python 3 functionality to Python 2.) + +2. ``GeneratorContextManager`` objects do not preserve the signature of + the decorated functions. The decorated ``hello`` function above will + have the generic signature ``hello(*args, **kwargs)``, but fails if + called with more than zero arguments. + +For these reasons, the `decorator` module, starting from release 3.4, offers a +``decorator.contextmanager`` decorator that solves both problems, +*and* works in all supported Python versions. Its usage is identical, +and factories decorated with ``decorator.contextmanager`` will return +instances of ``ContextManager``, a subclass of the standard library's +``contextlib.GeneratorContextManager`` class. The subclass includes +an improved ``__call__`` method, which acts as a signature-preserving +decorator. + +The ``FunctionMaker`` class +--------------------------------------------------------------- + +You may wonder how the functionality of the ``decorator`` module +is implemented. The basic building block is +a ``FunctionMaker`` class. It generates on-the-fly functions +with a given name and signature from a function template +passed as a string. + +If you're just writing ordinary decorators, then you probably won't +need to use ``FunctionMaker`` directly. But in some circumstances, it +can be handy. You will see an example shortly--in +the implementation of a cool decorator utility (``decorator_apply``). + +``FunctionMaker`` provides the ``.create`` classmethod, which +accepts the *name*, *signature*, and *body* of the function +you want to generate, as well as the execution environment +where the function is generated by ``exec``. + +Here's an example: + +```python +>>> def f(*args, **kw): # a function with a generic signature +... print(args, kw) + +>>> f1 = FunctionMaker.create('f1(a, b)', 'f(a, b)', dict(f=f)) +>>> f1(1,2) +(1, 2) {} + +``` + +It is important to notice that the function body is interpolated +before being executed; **be careful** with the ``%`` sign! + +``FunctionMaker.create`` also accepts keyword arguments. +The keyword arguments are attached to the generated function. +This is useful if you want to set some function attributes +(e.g., the docstring ``__doc__``). + +For debugging/introspection purposes, it may be useful to see +the source code of the generated function. To do this, just +pass ``addsource=True``, and the generated function will get +a ``__source__`` attribute: + +```python +>>> f1 = FunctionMaker.create( +... 'f1(a, b)', 'f(a, b)', dict(f=f), addsource=True) +>>> print(f1.__source__) +def f1(a, b): + f(a, b) +<BLANKLINE> + +``` + +The first argument to ``FunctionMaker.create`` can be a string (as above), +or a function. This is the most common usage, since you typically decorate +pre-existing functions. + +If you're writing a framework, however, you may want to use +``FunctionMaker.create`` directly, rather than ``decorator``, because it gives +you direct access to the body of the generated function. + +For instance, suppose you want to instrument the ``__init__`` methods of a +set of classes, by preserving their signature. +(This use case is not made up. This is done by SQAlchemy, and other frameworks, +too.) +Here is what happens: + +- If first argument of ``FunctionMaker.create`` is a function, + an instance of ``FunctionMaker`` is created with the attributes + ``args``, ``varargs``, ``keywords``, and ``defaults``. + (These mirror the return values of the standard library's + ``inspect.getfullargspec``.) + +- For each item in ``args`` (a list of strings of the names of all required + arguments), an attribute ``arg0``, ``arg1``, ..., ``argN`` is also generated. + +- Finally, there is a ``signature`` attribute, which is a string with the + signature of the original function. + +**NOTE:** You should not pass signature strings with default arguments +(e.g., something like ``'f1(a, b=None)'``). Just pass ``'f1(a, b)'``, +followed by a tuple of defaults: + +```python +>>> f1 = FunctionMaker.create( +... 'f1(a, b)', 'f(a, b)', dict(f=f), addsource=True, defaults=(None,)) +>>> print(getfullargspec(f1)) +FullArgSpec(args=['a', 'b'], varargs=None, varkw=None, defaults=(None,), kwonlyargs=[], kwonlydefaults=None, annotations={}) + +``` + +Getting the source code +--------------------------------------------------- + +Internally, ``FunctionMaker.create`` uses ``exec`` to generate the +decorated function. Therefore ``inspect.getsource`` will not work for +decorated functions. In IPython, this means that the usual ``??`` trick +will give you the (right on the spot) message ``Dynamically generated +function. No source code available``. + +In the past, I considered this acceptable, since ``inspect.getsource`` +does not really work with "regular" decorators. In those cases, +``inspect.getsource`` gives you the wrapper source code, which is probably +not what you want: + +$$identity_dec +$$example + +```python +>>> import inspect +>>> print(inspect.getsource(example)) + def wrapper(*args, **kw): + return func(*args, **kw) +<BLANKLINE> + +``` + +(See bug report [1764286](http://bugs.python.org/issue1764286) +for an explanation of what is happening). +Unfortunately the bug still exists in all versions of Python < 3.5. + +However, there is a workaround. The decorated function has the ``__wrapped__`` +attribute, pointing to the original function. The simplest way to get the +source code is to call ``inspect.getsource`` on the undecorated function: + +```python +>>> print(inspect.getsource(factorial.__wrapped__)) +@tail_recursive +def factorial(n, acc=1): + "The good old factorial" + if n == 0: + return acc + return factorial(n-1, n*acc) +<BLANKLINE> + +``` + +Dealing with third-party decorators +----------------------------------------------------------------- + +Sometimes on the net you find some cool decorator that you would +like to include in your code. However, more often than not, the cool +decorator is not signature-preserving. What you need is an easy way to +upgrade third party decorators to signature-preserving decorators... +*without* having to rewrite them in terms of ``decorator``. + +You can use a ``FunctionMaker`` to implement that functionality as follows: + +$$decorator_apply + +``decorator_apply`` sets the generated function's ``__wrapped__`` attribute +to the original function, so you can get the right source code. +If you are using a Python later than 3.2, you should also set the +``__qualname__`` attribute to preserve the qualified name of the original +function. + +Notice that I am not providing this functionality in the ``decorator`` +module directly, since I think it is best to rewrite the decorator instead +of adding another level of indirection. However, practicality +beats purity, so you can add ``decorator_apply`` to your toolbox and +use it if you need to. + +To give a good example for ``decorator_apply``, I will show a pretty slick +decorator that converts a tail-recursive function into an iterative function. +I have shamelessly stolen the core concept from Kay Schluehr's recipe +in the Python Cookbook, +http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496691. + +$$TailRecursive + +Here the decorator is implemented as a class returning callable +objects. + +$$tail_recursive + +Here is how you apply the upgraded decorator to the good old factorial: + +$$factorial + +```python +>>> print(factorial(4)) +24 + +``` + +This decorator is pretty impressive, and should give you some food for +thought! ;) + +Notice that there is no recursion limit now; you can easily compute +``factorial(1001)`` (or larger) without filling the stack frame. + +Notice also that the decorator will *not* work on functions which +are not tail recursive, such as the following: + +$$fact + +**Reminder:** A function is *tail recursive* if it does either of the +following: + +- returns a value without making a recursive call; or, +- returns directly the result of a recursive call. + +Python 3.5 coroutines +----------------------- + +I am personally not using Python 3.5 coroutines yet, because at work we are +still maintaining compatibility with Python 2.7. However, some users requested +support for coroutines and since version 4.1 the decorator module has it. +You should consider the support experimental and kindly report issues if +you find any. + +Here I will give a single example of usage. Suppose you want to log the moment +a coroutine starts and the moment it stops for debugging purposes. You could +write code like the following: + +```python +import time +import logging +from asyncio import get_event_loop, sleep, wait +from decorator import decorator + + @decorator +async def log_start_stop(coro, *args, **kwargs): + logging.info('Starting %s%s', coro.__name__, args) + t0 = time.time() + await coro(*args, **kwargs) + dt = time.time() - t0 + logging.info('Ending %s%s after %d seconds', coro.__name__, args, dt) + +@log_start_stop +async def make_task(n): + for i in range(n): + await sleep(1) + +if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + tasks = [make_task(3), make_task(2), make_task(1)] + get_event_loop().run_until_complete(wait(tasks)) +``` + +and you will get an output like this: + +```bash +INFO:root:Starting make_task(1,) +INFO:root:Starting make_task(3,) +INFO:root:Starting make_task(2,) +INFO:root:Ending make_task(1,) after 1 seconds +INFO:root:Ending make_task(2,) after 2 seconds +INFO:root:Ending make_task(3,) after 3 seconds +``` + +This may be handy if you have trouble understanding what it going on +with a particularly complex chain of coroutines. With a single line you +can decorate the troubling coroutine function, understand what happens, fix the +issue and then remove the decorator (or keep it if continuous monitoring +of the coroutines makes sense). Notice that +``inspect.iscoroutinefunction(make_task)`` +will return the right answer (i.e. ``True``). + +It is also possible to define decorators converting coroutine functions +into regular functions, such as the following: + +```python +@decorator +def coro_to_func(coro, *args, **kw): + "Convert a coroutine into a function" + return get_event_loop().run_until_complete(coro(*args, **kw)) +``` + +Notice the diffence: the caller in ``log_start_stop`` was a coroutine +function and the associate decorator was converting coroutines->coroutines; +the caller in ``coro_to_func`` is a regular function and converts +coroutines -> functions. + +Multiple dispatch +------------------------------------------- + +There has been talk of implementing multiple dispatch functions +(i.e. "generic functions") in Python for over ten years. Last year, +something concrete was done for the first time. As of Python 3.4, +we have the decorator ``functools.singledispatch`` to implement generic +functions! + +As its name implies, it is limited to *single dispatch*; in other words, +it is able to dispatch on the first argument of the function only. + +The ``decorator`` module provides the decorator factory ``dispatch_on``, +which can be used to implement generic functions dispatching on *any* argument. +Moreover, it can manage dispatching on more than one argument. +(And, of course, it is signature-preserving.) + +Here is a concrete example (from a real-life use case) where it is desiderable +to dispatch on the second argument. + +Suppose you have an ``XMLWriter`` class, which is instantiated +with some configuration parameters, and has the ``.write`` method which +serializes objects to XML: + +$$XMLWriter + +Here, you want to dispatch on the *second* argument; the first is already +taken by ``self``. The ``dispatch_on`` decorator factory allows you to specify +the dispatch argument simply by passing its name as a string. (Note +that if you misspell the name you will get an error.) + +The decorated function `write` is turned into a generic function ( +`write` is a function at the idea it is decorated; it will be turned +into a method later, at class instantiation time), +and it is called if there are no more specialized implementations. + +Usually, default functions should raise a ``NotImplementedError``, thus +forcing people to register some implementation. +You can perform the registration with a decorator: + +$$writefloat + +Now ``XMLWriter`` can serialize floats: + +```python +>>> writer = XMLWriter() +>>> writer.write(2.3) +'<float>2.3</float>' + +``` + +I could give a down-to-earth example of situations in which it is desiderable +to dispatch on more than one argument--for instance, I once implemented +a database-access library where the first dispatching argument was the +the database driver, and the second was the database record--but here +I will follow tradition, and show the time-honored Rock-Paper-Scissors example: + +$$Rock +$$Paper +$$Scissors + +I have added an ordinal to the Rock-Paper-Scissors classes to simplify +the implementation. The idea is to define a generic function (``win(a, +b)``) of two arguments corresponding to the *moves* of the first and +second players. The *moves* are instances of the classes +Rock, Paper, and Scissors: + +- Paper wins over Rock +- Scissors wins over Paper +- Rock wins over Scissors + +The function will return +1 for a win, -1 for a loss, and 0 for parity. +There are 9 combinations, but combinations with the same ordinal +(i.e. the same class) return 0. Moreover, by exchanging the order of the +arguments, the sign of the result changes. Therefore, it is sufficient to +directly specify only 3 implementations: + +$$win +$$winRockPaper +$$winPaperScissors +$$winRockScissors + +Here is the result: + +```python +>>> win(Paper(), Rock()) +1 +>>> win(Scissors(), Paper()) +1 +>>> win(Rock(), Scissors()) +1 +>>> win(Paper(), Paper()) +0 +>>> win(Rock(), Rock()) +0 +>>> win(Scissors(), Scissors()) +0 +>>> win(Rock(), Paper()) +-1 +>>> win(Paper(), Scissors()) +-1 +>>> win(Scissors(), Rock()) +-1 + +``` + +The point of generic functions is that they play well with subclassing. +For instance, suppose we define a ``StrongRock``, which does not lose against +Paper: + +$$StrongRock +$$winStrongRockPaper + +Then you do not need to define other implementations; they are +inherited from the parent: + +```python +>>> win(StrongRock(), Scissors()) +1 + +``` + +You can introspect the precedence used by the dispath algorithm by +calling ``.dispatch_info(*types)``: + +```python +>>> win.dispatch_info(StrongRock, Scissors) +[('StrongRock', 'Scissors'), ('Rock', 'Scissors')] + +``` + +Since there is no direct implementation for (``StrongRock``, ``Scissors``), +the dispatcher will look at the implementation for (``Rock``, ``Scissors``) +which is available. Internally, the algorithm is doing a cross +product of the class precedence lists (or *Method Resolution Orders*, +[MRO](http://www.python.org/2.3/mro.html) for short) of ``StrongRock`` + and ``Scissors``, respectively. + +Generic functions and virtual ancestors +------------------------------------------------- + +In Python, generic functions are complicated by the existence of +"virtual ancestors": superclasses which are not in the class hierarchy. + +Consider this class: + +$$WithLength + +This class defines a ``__len__`` method, and is therefore +considered to be a subclass of the abstract base class +``collections.abc.Sized`` (``collections.Sized`` on Python 2): + +```python +>>> issubclass(WithLength, collections.abc.Sized) +True + +``` + +However, ``collections.abc.Sized`` is not in the MRO_ of ``WithLength``; it +is not a true ancestor. Any implementation of generic functions (even +with single dispatch) must go through some contorsion to take into +account the virtual ancestors. + +In particular, if we define a generic function... + +$$get_length + +...implemented on all classes with a length... + +$$get_length_sized + +...then ``get_length`` must be defined on ``WithLength`` instances... + +```python +>>> get_length(WithLength()) +0 + +``` + +...even if ``collections.abc.Sized`` is not a true ancestor of ``WithLength``. + +Of course, this is a contrived example--you could just use the +builtin ``len``--but you should get the idea. + +Since in Python it is possible to consider any instance of ``ABCMeta`` +as a virtual ancestor of any other class (it is enough to register it +as ``ancestor.register(cls)``), any implementation of generic functions +must be aware of the registration mechanism. + +For example, suppose you are using a third-party set-like class, like +the following: + +$$SomeSet + +Here, the author of ``SomeSet`` made a mistake by inheriting from +``collections.abc.Sized`` (instead of ``collections.abc.Set``). + +This is not a problem. You can register *a posteriori* +``collections.abc.Set`` as a virtual ancestor of ``SomeSet``: + +```python +>>> _ = collections.abc.Set.register(SomeSet) +>>> issubclass(SomeSet, collections.abc.Set) +True + +``` + +Now, let's define an implementation of ``get_length`` specific to set: + +$$get_length_set + +The current implementation (and ``functools.singledispatch`` too) +is able to discern that a ``Set`` is a ``Sized`` object, by looking at +the class registry, so it uses the more specific implementation for ``Set``: + +```python +>>> get_length(SomeSet()) # NB: the implementation for Sized would give 0 +1 + +``` + +Sometimes it is not clear how to dispatch. For instance, consider a +class ``C`` registered both as ``collections.abc.Iterable`` and +``collections.abc.Sized``, and defines a generic function ``g`` with +implementations for both ``collections.abc.Iterable`` *and* +``collections.abc.Sized``: + +$$singledispatch_example1 + +It is impossible to decide which implementation to use, since the ancestors +are independent. The following function will raise a ``RuntimeError`` +when called. This is consistent with the "refuse the temptation to guess" +philosophy. ``functools.singledispatch`` would raise a similar error. + +It would be easy to rely on the order of registration to decide the +precedence order. This is reasonable, but also fragile: + +- if, during some refactoring, you change the registration order by mistake, + a different implementation could be taken; +- if implementations of the generic functions are distributed across modules, + and you change the import order, a different implementation could be taken. + +So the ``decorator`` module prefers to raise an error in the face of ambiguity. +This is the same approach taken by the standard library. + +However, it should be noted that the *dispatch algorithm* used by the decorator +module is different from the one used by the standard library, so in certain +cases you will get different answers. The difference is that +``functools.singledispatch`` tries to insert the virtual ancestors *before* the +base classes, whereas ``decorator.dispatch_on`` tries to insert them *after* +the base classes. + +Here's an example that shows the difference: + +$$singledispatch_example2 + +If you play with this example and replace the ``singledispatch`` definition +with ``functools.singledispatch``, the assertion will break: ``g`` will return +``"container"`` instead of ``"s"``, because ``functools.singledispatch`` +will insert the ``Container`` class right before ``S``. + +Notice that here I am not making any bold claim such as "the standard +library algorithm is wrong and my algorithm is right" or viceversa. It +just point out that there are some subtle differences. The only way to +understand what is really happening here is to scratch your head by +looking at the implementations. I will just notice that +``.dispatch_info`` is quite essential to see the class precedence +list used by algorithm: + +```python +>>> g, V = singledispatch_example2() +>>> g.dispatch_info(V) +[('V',), ('Sized',), ('S',), ('Container',)] + +``` + +The current implementation does not implement any kind of cooperation +between implementations. In other words, nothing is akin either to +call-next-method in Lisp, or to ``super`` in Python. + +Finally, let me notice that the decorator module implementation does +not use any cache, whereas the ``singledispatch`` implementation does. + +Caveats and limitations +------------------------------------------- + +One thing you should be aware of, is the performance penalty of decorators. +The worse case is shown by the following example: + +```bash + $ cat performance.sh + python3 -m timeit -s " + from decorator import decorator + + @decorator + def do_nothing(func, *args, **kw): + return func(*args, **kw) + + @do_nothing + def f(): + pass + " "f()" + + python3 -m timeit -s " + def f(): + pass + " "f()" + +``` +On my laptop, using the ``do_nothing`` decorator instead of the +plain function is five times slower: + +```bash + $ bash performance.sh + 1000000 loops, best of 3: 1.39 usec per loop + 1000000 loops, best of 3: 0.278 usec per loop +``` +Of course, a real life function probably does something more useful +than the function ``f`` here, so the real life performance penalty +*could* be negligible. As always, the only way to know if there is a +penalty in your specific use case is to measure it. + +More importantly, you should be aware that decorators will make your +tracebacks longer and more difficult to understand. + +Consider this example: + +```python +>>> @trace +... def f(): +... 1/0 + +``` + +Calling ``f()`` gives you a ``ZeroDivisionError``. +But since the function is decorated, the traceback is longer: + +```python +>>> f() # doctest: +ELLIPSIS +Traceback (most recent call last): + ... + File "<string>", line 2, in f + File "<doctest __main__[22]>", line 4, in trace + return f(*args, **kw) + File "<doctest __main__[51]>", line 3, in f + 1/0 +ZeroDivisionError: ... + +``` + +You see here the inner call to the decorator ``trace``, which calls +``f(*args, **kw)``, and a reference to ``File "<string>", line 2, in f``. + +This latter reference is due to the fact that, internally, the decorator +module uses ``exec`` to generate the decorated function. Notice that +``exec`` is *not* responsible for the performance penalty, since is the +called *only once* (at function decoration time); it is *not* called +each time the decorated function is called. + +Presently, there is no clean way to avoid ``exec``. A clean solution +would require changing the CPython implementation, by +adding a hook to functions (to allow changing their signature directly). + +Even in Python 3.5, it is impossible to change the +function signature directly. Thus, the ``decorator`` module is +still useful! As a matter of fact, this is the main reason why I still +maintain the module and release new versions. + +It should be noted that in Python 3.5, a *lot* of improvements have +been made: you can decorate a function with +``func_tools.update_wrapper``, and ``pydoc`` will see the correct +signature. Unfortunately, the function will still have an incorrect +signature internally, as you can see by using +``inspect.getfullargspec``; so, all documentation tools using +``inspect.getfullargspec`` - which has been rightly deprecated - +will see the wrong signature. + +In the present implementation, decorators generated by ``decorator`` +can only be used on user-defined Python functions or methods. +They cannot be used on generic callable objects or built-in functions, +due to limitations of the standard library's ``inspect`` module, especially +for Python 2. In Python 3.5, many such limitations have been removed, but +I still think that it is cleaner and safer to decorate only functions and +coroutines. If you want to decorate things like classmethods/staticmethods +and general callables - which I will never support in the decorator module - +I suggest you to look at the [wrapt](https://wrapt.readthedocs.io/en/latest/) +project by Graeme Dumpleton. + +There is a strange quirk when decorating functions with keyword +arguments, if one of the arguments has the same name used in the +caller function for the first argument. The quirk was reported by +David Goldstein. + +Here is an example where it is manifest: + +```python +>>> @memoize +... def getkeys(**kw): +... return kw.keys() + +>>> getkeys(func='a') # doctest: +ELLIPSIS +Traceback (most recent call last): + ... +TypeError: _memoize() got multiple values for ... 'func' + +``` + +The error message looks really strange... until you realize that +the caller function `_memoize` uses `func` as first argument, +so there is a confusion between the positional argument and the +keywork arguments. + +The solution is to change the name of the first argument in `_memoize`, +or to change the implementation like so: + +```python + +def _memoize(*all_args, **kw): + func = all_args[0] + args = all_args[1:] + if kw: # frozenset is used to ensure hashability + key = args, frozenset(kw.items()) + else: + key = args + cache = func.cache # attribute added by memoize + if key not in cache: + cache[key] = func(*args, **kw) + return cache[key] +``` + +This avoids the need to name the first argument, so the problem +simply disappears. This is a technique that you should keep in mind +when writing decorators for functions with keyword arguments. Also, +notice that lately I have come to believe that decorating functions with +keyword arguments is not such a good idea, and you may want not to do +that. + +On a similar note, there is a restriction on argument names. For instance, +if you name an argument ``_call_`` or ``_func_``, you will get a ``NameError``: + +```python +>>> @trace +... def f(_func_): print(f) +... +Traceback (most recent call last): + ... +NameError: _func_ is overridden in +def f(_func_): + return _call_(_func_, _func_) + +``` + +Finally, the implementation is such that the decorated function makes +a (shallow) copy of the original function dictionary: + +```python +>>> def f(): pass # the original function +>>> f.attr1 = "something" # setting an attribute +>>> f.attr2 = "something else" # setting another attribute + +>>> traced_f = trace(f) # the decorated function + +>>> traced_f.attr1 +'something' +>>> traced_f.attr2 = "something different" # setting attr +>>> f.attr2 # the original attribute did not change +'something else' + +``` + +LICENSE (2-clause BSD) +--------------------------------------------- + +Copyright (c) 2005-2020, 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. + +If you use this software and you are happy with it, consider sending me a +note, just to gratify my ego. On the other hand, if you use this software and +you are unhappy with it, send me a patch! +""" + +function_annotations = """Function annotations +--------------------------------------------- + +Python 3 introduced the concept of [function annotations]( +http://www.python.org/dev/peps/pep-3107/): the ability +to annotate the signature of a function with additional information, +stored in a dictionary named ``__annotations__``. The ``decorator`` module +(starting from release 3.3) will understand and preserve these annotations. + +Here is an example: + +```python +>>> @trace +... def f(x: 'the first argument', y: 'default argument'=1, z=2, +... *args: 'varargs', **kw: 'kwargs'): +... pass + +``` + +In order to introspect functions with annotations, one needs the +utility ``inspect.getfullargspec`` (introduced in Python 3, then +deprecated in Python 3.5, then undeprecated in Python 3.6): + +```python +>>> from inspect import getfullargspec +>>> argspec = getfullargspec(f) +>>> argspec.args +['x', 'y', 'z'] +>>> argspec.varargs +'args' +>>> argspec.varkw +'kw' +>>> argspec.defaults +(1, 2) +>>> argspec.kwonlyargs +[] +>>> argspec.kwonlydefaults + +``` + +You can check that the ``__annotations__`` dictionary is preserved: + +```python +>>> f.__annotations__ is f.__wrapped__.__annotations__ +True + +``` + +Here ``f.__wrapped__`` is the original undecorated function. +This attribute exists for consistency with the behavior of +``functools.update_wrapper``. + +Another attribute copied from the original function is ``__qualname__``, +the qualified name. This attribute was introduced in Python 3.3. +""" + +if sys.version_info < (3,): + function_annotations = '' + +today = time.strftime('%Y-%m-%d') + +__doc__ = (doc.replace('$VERSION', __version__).replace('$DATE', today) + .replace('$FUNCTION_ANNOTATIONS', function_annotations)) + + +def decorator_apply(dec, func): + """ + Decorate a function by preserving the signature even if dec + is not a signature-preserving decorator. + """ + return FunctionMaker.create( + func, 'return decfunc(%(signature)s)', + dict(decfunc=dec(func)), __wrapped__=func) + + +def _trace(f, *args, **kw): + kwstr = ', '.join('%r: %r' % (k, kw[k]) for k in sorted(kw)) + print("calling %s with args %s, {%s}" % (f.__name__, args, kwstr)) + return f(*args, **kw) + + +def trace(f): + return decorate(f, _trace) + + +class Future(threading.Thread): + """ + A class converting blocking functions into asynchronous + functions by using threads. + """ + def __init__(self, func, *args, **kw): + try: + counter = func.counter + except AttributeError: # instantiate the counter at the first call + counter = func.counter = itertools.count(1) + name = '%s-%s' % (func.__name__, next(counter)) + + def func_wrapper(): + self._result = func(*args, **kw) + super(Future, self).__init__(target=func_wrapper, name=name) + self.start() + + def result(self): + self.join() + return self._result + + +def identity_dec(func): + def wrapper(*args, **kw): + return func(*args, **kw) + return wrapper + + +@identity_dec +def example(): + pass + + +def memoize_uw(func): + func.cache = {} + + def memoize(*args, **kw): + if kw: # frozenset is used to ensure hashability + key = args, frozenset(kw.items()) + else: + key = args + if key not in func.cache: + func.cache[key] = func(*args, **kw) + return func.cache[key] + return functools.update_wrapper(memoize, func) + + +@memoize_uw +def f1(x): + "Simulate some long computation" + time.sleep(1) + return x + + +def _memoize(func, *args, **kw): + if kw: # frozenset is used to ensure hashability + key = args, frozenset(kw.items()) + else: + key = args + cache = func.cache # attribute added by memoize + if key not in cache: + cache[key] = func(*args, **kw) + return cache[key] + + +def memoize(f): + """ + A simple memoize implementation. It works by adding a .cache dictionary + to the decorated function. The cache will grow indefinitely, so it is + your responsibility to clear it, if needed. + """ + f.cache = {} + return decorate(f, _memoize) + + +@decorator +def blocking(f, msg='blocking', *args, **kw): + if not hasattr(f, "thread"): # no thread running + def set_result(): + f.result = f(*args, **kw) + f.thread = threading.Thread(None, set_result) + f.thread.start() + return msg + elif f.thread.is_alive(): + return msg + else: # the thread is ended, return the stored result + del f.thread + return f.result + + +class User(object): + "Will just be able to see a page" + + +class PowerUser(User): + "Will be able to add new pages too" + + +class Admin(PowerUser): + "Will be able to delete pages too" + + +class PermissionError(Exception): + """ + >>> a = Action() + >>> a.user = User() + >>> a.view() # ok + >>> a.insert() # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + PermissionError: User does not have the permission to run insert! + """ + + +@decorator +def restricted(func, user_class=User, *args, **kw): + "Restrict access to a given class of users" + self = args[0] + if isinstance(self.user, user_class): + return func(*args, **kw) + else: + raise PermissionError( + '%s does not have the permission to run %s!' + % (self.user, func.__name__)) + + +class Action(object): + @restricted(user_class=User) + def view(self): + "Any user can view objects" + + @restricted(user_class=PowerUser) + def insert(self): + "Only power users can insert objects" + + @restricted(user_class=Admin) + def delete(self): + "Only the admin can delete objects" + + +class TailRecursive(object): + """ + tail_recursive decorator based on Kay Schluehr's recipe + http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496691 + with improvements by me and George Sakkis. + """ + + def __init__(self, func): + self.func = func + self.firstcall = True + self.CONTINUE = object() # sentinel + + def __call__(self, *args, **kwd): + CONTINUE = self.CONTINUE + if self.firstcall: + func = self.func + self.firstcall = False + try: + while True: + result = func(*args, **kwd) + if result is CONTINUE: # update arguments + args, kwd = self.argskwd + else: # last call + return result + finally: + self.firstcall = True + else: # return the arguments of the tail call + self.argskwd = args, kwd + return CONTINUE + + +def tail_recursive(func): + return decorator_apply(TailRecursive, func) + + +@tail_recursive +def factorial(n, acc=1): + "The good old factorial" + if n == 0: + return acc + return factorial(n-1, n*acc) + + +def fact(n): # this is not tail-recursive + if n == 0: + return 1 + return n * fact(n-1) + + +def a_test_for_pylons(): + """ + In version 3.1.0 decorator(caller) returned a nameless partial + object, thus breaking Pylons. That must not happen again. + + >>> decorator(_memoize).__name__ + '_memoize' + + Here is another bug of version 3.1.1 missing the docstring: + + >>> factorial.__doc__ + 'The good old factorial' + """ + + +if sys.version_info >= (3,): # tests for signatures specific to Python 3 + + def test_kwonlydefaults(): + """ + >>> @trace + ... def f(arg, defarg=1, *args, kwonly=2): pass + ... + >>> f.__kwdefaults__ + {'kwonly': 2} + """ + + def test_kwonlyargs(): + """ + >>> @trace + ... def func(a, b, *args, y=2, z=3, **kwargs): + ... return y, z + ... + >>> func('a', 'b', 'c', 'd', 'e', y='y', z='z', cat='dog') + calling func with args ('a', 'b', 'c', 'd', 'e'), {'cat': 'dog', 'y': 'y', 'z': 'z'} + ('y', 'z') + """ + + def test_kwonly_no_args(): + """# this was broken with decorator 3.3.3 + >>> @trace + ... def f(**kw): pass + ... + >>> f() + calling f with args (), {} + """ + + def test_kwonly_star_notation(): + """ + >>> @trace + ... def f(*, a=1, **kw): pass + ... + >>> import inspect + >>> inspect.getfullargspec(f) + FullArgSpec(args=[], varargs=None, varkw='kw', defaults=None, kwonlyargs=['a'], kwonlydefaults={'a': 1}, annotations={}) + """ + + +@contextmanager +def before_after(before, after): + print(before) + yield + print(after) + + +ba = before_after('BEFORE', 'AFTER') # ContextManager instance + + +@ba +def hello(user): + """ + >>> ba.__class__.__name__ + 'ContextManager' + >>> hello('michele') + BEFORE + hello michele + AFTER + """ + print('hello %s' % user) + + +# ####################### multiple dispatch ############################ # + + +class XMLWriter(object): + def __init__(self, **config): + self.cfg = config + + @dispatch_on('obj') + def write(self, obj): + raise NotImplementedError(type(obj)) + + +@XMLWriter.write.register(float) +def writefloat(self, obj): + return '<float>%s</float>' % obj + + +class Rock(object): + ordinal = 0 + + +class Paper(object): + ordinal = 1 + + +class Scissors(object): + ordinal = 2 + + +class StrongRock(Rock): + pass + + +@dispatch_on('a', 'b') +def win(a, b): + if a.ordinal == b.ordinal: + return 0 + elif a.ordinal > b.ordinal: + return -win(b, a) + raise NotImplementedError((type(a), type(b))) + + +@win.register(Rock, Paper) +def winRockPaper(a, b): + return -1 + + +@win.register(Rock, Scissors) +def winRockScissors(a, b): + return 1 + + +@win.register(Paper, Scissors) +def winPaperScissors(a, b): + return -1 + + +@win.register(StrongRock, Paper) +def winStrongRockPaper(a, b): + return 0 + + +class WithLength(object): + def __len__(self): + return 0 + + +class SomeSet(collections.abc.Sized): + # methods that make SomeSet set-like + # not shown ... + def __len__(self): + return 0 + + +@dispatch_on('obj') +def get_length(obj): + raise NotImplementedError(type(obj)) + + +@get_length.register(collections.abc.Sized) +def get_length_sized(obj): + return len(obj) + + +@get_length.register(collections.abc.Set) +def get_length_set(obj): + return 1 + + +class C(object): + "Registered as Sized and Iterable" + + +collections.abc.Sized.register(C) +collections.abc.Iterable.register(C) + + +def singledispatch_example1(): + singledispatch = dispatch_on('obj') + + @singledispatch + def g(obj): + raise NotImplementedError(type(g)) + + @g.register(collections.abc.Sized) + def g_sized(object): + return "sized" + + @g.register(collections.abc.Iterable) + def g_iterable(object): + return "iterable" + + g(C()) # RuntimeError: Ambiguous dispatch: Iterable or Sized? + + +def singledispatch_example2(): + # adapted from functools.singledispatch test case + singledispatch = dispatch_on('arg') + + class S(object): + pass + + class V(c.Sized, S): + def __len__(self): + return 0 + + @singledispatch + def g(arg): + return "base" + + @g.register(S) + def g_s(arg): + return "s" + + @g.register(c.Container) + def g_container(arg): + return "container" + + v = V() + assert g(v) == "s" + c.Container.register(V) # add c.Container to the virtual mro of V + assert g(v) == "s" # since the virtual mro is V, Sized, S, Container + return g, V + + +@decorator +def warn_slow(func, duration=0, *args, **kwargs): + t0 = time.time() + res = func(*args, **kwargs) + dt = time.time() - t0 + if dt >= duration: + print('%s is slow' % func.__name__) + return res + + +@warn_slow() # with parens +def operation1(): + """ + >>> operation1() + operation1 is slow + """ + time.sleep(.1) + + +@warn_slow # without parens +def operation2(): + """ + >>> operation2() + operation2 is slow + """ + time.sleep(.1) + + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/contrib/python/decorator/py2/tests/test.py b/contrib/python/decorator/py2/tests/test.py new file mode 100644 index 0000000000..7ddfaf45b9 --- /dev/null +++ b/contrib/python/decorator/py2/tests/test.py @@ -0,0 +1,480 @@ +from __future__ import absolute_import +import sys +import doctest +import unittest +import decimal +import inspect +import functools +import collections +from collections import defaultdict +try: + c = collections.abc +except AttributeError: + c = collections +from decorator import dispatch_on, contextmanager, decorator +try: + from . import documentation as doc +except (ImportError, ValueError, SystemError): # depending on the py-version + import documentation as doc + + +@contextmanager +def assertRaises(etype): + """This works in Python 2.6 too""" + try: + yield + except etype: + pass + else: + raise Exception('Expected %s' % etype.__name__) + + +if sys.version_info >= (3, 5): + exec('''from asyncio import get_event_loop + +@decorator +async def before_after(coro, *args, **kwargs): + return "<before>" + (await coro(*args, **kwargs)) + "<after>" + +@decorator +def coro_to_func(coro, *args, **kw): + return get_event_loop().run_until_complete(coro(*args, **kw)) + +class CoroutineTestCase(unittest.TestCase): + def test_before_after(self): + @before_after + async def coro(x): + return x + self.assertTrue(inspect.iscoroutinefunction(coro)) + out = get_event_loop().run_until_complete(coro('x')) + self.assertEqual(out, '<before>x<after>') + + def test_coro_to_func(self): + @coro_to_func + async def coro(x): + return x + self.assertFalse(inspect.iscoroutinefunction(coro)) + self.assertEqual(coro('x'), 'x') +''') + + +def gen123(): + yield 1 + yield 2 + yield 3 + + +class GeneratorCallerTestCase(unittest.TestCase): + def test_gen123(self): + @decorator + def square(func, *args, **kw): + for x in gen123(): + yield x * x + new = square(gen123) + self.assertTrue(inspect.isgeneratorfunction(new)) + self.assertEqual(list(new()), [1, 4, 9]) + + +class DocumentationTestCase(unittest.TestCase): + def test(self): + err = doctest.testmod(doc)[0] + self.assertEqual(err, 0) + + def test_singledispatch1(self): + if hasattr(functools, 'singledispatch'): + with assertRaises(RuntimeError): + doc.singledispatch_example1() + + def test_singledispatch2(self): + if hasattr(functools, 'singledispatch'): + doc.singledispatch_example2() + + +class ExtraTestCase(unittest.TestCase): + def test_qualname(self): + if sys.version_info >= (3, 3): + self.assertEqual(doc.hello.__qualname__, 'hello') + else: + with assertRaises(AttributeError): + doc.hello.__qualname__ + + def test_signature(self): + if hasattr(inspect, 'signature'): + sig = inspect.signature(doc.f1) + self.assertEqual(str(sig), '(x)') + + def test_unique_filenames(self): + @decorator + def d1(f, *args, **kwargs): + return f(*args, **kwargs) + + @decorator + def d2(f, *args, **kwargs): + return f(*args, **kwargs) + + @d1 + def f1(x, y, z): + pass + + @d2 + def f2(x, y, z): + pass + + f1_orig = f1 + + @d1 + def f1(x, y, z): + pass + self.assertNotEqual(d1.__code__.co_filename, d2.__code__.co_filename) + self.assertNotEqual(f1.__code__.co_filename, f2.__code__.co_filename) + self.assertNotEqual(f1_orig.__code__.co_filename, + f1.__code__.co_filename) + + def test_no_first_arg(self): + @decorator + def example(*args, **kw): + return args[0](*args[1:], **kw) + + @example + def func(**kw): + return kw + + # there is no confusion when passing args as a keyword argument + self.assertEqual(func(args='a'), {'args': 'a'}) + + def test_decorator_factory(self): + # similar to what IPython is doing in traitlets.config.application + @decorator + def catch_config_error(method, app, *args, **kwargs): + return method(app) + catch_config_error(lambda app: None) + + def test_add1(self): + # similar to what IPython is doing in traitlets.config.application + @decorator + def add(func, const=1, *args, **kwargs): + return const + func(*args, **kwargs) + + def f(x): + return x + self.assertEqual(add(f, 2)(0), 2) + + +# ################### test dispatch_on ############################# # +# adapted from test_functools in Python 3.5 +singledispatch = dispatch_on('obj') + + +class TestSingleDispatch(unittest.TestCase): + def test_simple_overloads(self): + @singledispatch + def g(obj): + return "base" + + @g.register(int) + def g_int(i): + return "integer" + + self.assertEqual(g("str"), "base") + self.assertEqual(g(1), "integer") + self.assertEqual(g([1, 2, 3]), "base") + + def test_mro(self): + @singledispatch + def g(obj): + return "base" + + class A(object): + pass + + class C(A): + pass + + class B(A): + pass + + class D(C, B): + pass + + @g.register(A) + def g_A(a): + return "A" + + @g.register(B) + def g_B(b): + return "B" + + self.assertEqual(g(A()), "A") + self.assertEqual(g(B()), "B") + self.assertEqual(g(C()), "A") + self.assertEqual(g(D()), "B") + + def test_register_decorator(self): + @singledispatch + def g(obj): + return "base" + + @g.register(int) + def g_int(i): + return "int %s" % (i,) + self.assertEqual(g(""), "base") + self.assertEqual(g(12), "int 12") + + def test_register_error(self): + @singledispatch + def g(obj): + return "base" + + with assertRaises(TypeError): + # wrong number of arguments + @g.register(int) + def g_int(): + return "int" + + def test_wrapping_attributes(self): + @singledispatch + def g(obj): + "Simple test" + return "Test" + self.assertEqual(g.__name__, "g") + if sys.flags.optimize < 2: + self.assertEqual(g.__doc__, "Simple test") + + def test_c_classes(self): + @singledispatch + def g(obj): + return "base" + + @g.register(decimal.DecimalException) + def _(obj): + return obj.args + subn = decimal.Subnormal("Exponent < Emin") + rnd = decimal.Rounded("Number got rounded") + self.assertEqual(g(subn), ("Exponent < Emin",)) + self.assertEqual(g(rnd), ("Number got rounded",)) + + @g.register(decimal.Subnormal) + def _g(obj): + return "Too small to care." + self.assertEqual(g(subn), "Too small to care.") + self.assertEqual(g(rnd), ("Number got rounded",)) + + def test_register_abc(self): + d = {"a": "b"} + l = [1, 2, 3] + s = set([object(), None]) + f = frozenset(s) + t = (1, 2, 3) + + @singledispatch + def g(obj): + return "base" + + self.assertEqual(g(d), "base") + self.assertEqual(g(l), "base") + self.assertEqual(g(s), "base") + self.assertEqual(g(f), "base") + self.assertEqual(g(t), "base") + + g.register(c.Sized)(lambda obj: "sized") + self.assertEqual(g(d), "sized") + self.assertEqual(g(l), "sized") + self.assertEqual(g(s), "sized") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sized") + + g.register(c.MutableMapping)(lambda obj: "mutablemapping") + self.assertEqual(g(d), "mutablemapping") + self.assertEqual(g(l), "sized") + self.assertEqual(g(s), "sized") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sized") + + if hasattr(c, 'ChainMap'): + g.register(c.ChainMap)(lambda obj: "chainmap") + # irrelevant ABCs registered + self.assertEqual(g(d), "mutablemapping") + self.assertEqual(g(l), "sized") + self.assertEqual(g(s), "sized") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sized") + + g.register(c.MutableSequence)(lambda obj: "mutablesequence") + self.assertEqual(g(d), "mutablemapping") + self.assertEqual(g(l), "mutablesequence") + self.assertEqual(g(s), "sized") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sized") + + g.register(c.MutableSet)(lambda obj: "mutableset") + self.assertEqual(g(d), "mutablemapping") + self.assertEqual(g(l), "mutablesequence") + self.assertEqual(g(s), "mutableset") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sized") + + g.register(c.Mapping)(lambda obj: "mapping") + self.assertEqual(g(d), "mutablemapping") # not specific enough + self.assertEqual(g(l), "mutablesequence") + self.assertEqual(g(s), "mutableset") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sized") + + g.register(c.Sequence)(lambda obj: "sequence") + self.assertEqual(g(d), "mutablemapping") + self.assertEqual(g(l), "mutablesequence") + self.assertEqual(g(s), "mutableset") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sequence") + + g.register(c.Set)(lambda obj: "set") + self.assertEqual(g(d), "mutablemapping") + self.assertEqual(g(l), "mutablesequence") + self.assertEqual(g(s), "mutableset") + self.assertEqual(g(f), "set") + self.assertEqual(g(t), "sequence") + + g.register(dict)(lambda obj: "dict") + self.assertEqual(g(d), "dict") + self.assertEqual(g(l), "mutablesequence") + self.assertEqual(g(s), "mutableset") + self.assertEqual(g(f), "set") + self.assertEqual(g(t), "sequence") + + g.register(list)(lambda obj: "list") + self.assertEqual(g(d), "dict") + self.assertEqual(g(l), "list") + self.assertEqual(g(s), "mutableset") + self.assertEqual(g(f), "set") + self.assertEqual(g(t), "sequence") + + g.register(set)(lambda obj: "concrete-set") + self.assertEqual(g(d), "dict") + self.assertEqual(g(l), "list") + self.assertEqual(g(s), "concrete-set") + self.assertEqual(g(f), "set") + self.assertEqual(g(t), "sequence") + + g.register(frozenset)(lambda obj: "frozen-set") + self.assertEqual(g(d), "dict") + self.assertEqual(g(l), "list") + self.assertEqual(g(s), "concrete-set") + self.assertEqual(g(f), "frozen-set") + self.assertEqual(g(t), "sequence") + + g.register(tuple)(lambda obj: "tuple") + self.assertEqual(g(d), "dict") + self.assertEqual(g(l), "list") + self.assertEqual(g(s), "concrete-set") + self.assertEqual(g(f), "frozen-set") + self.assertEqual(g(t), "tuple") + + def test_mro_conflicts(self): + @singledispatch + def g(obj): + return "base" + + class O(c.Sized): + def __len__(self): + return 0 + o = O() + self.assertEqual(g(o), "base") + g.register(c.Iterable)(lambda arg: "iterable") + g.register(c.Container)(lambda arg: "container") + g.register(c.Sized)(lambda arg: "sized") + g.register(c.Set)(lambda arg: "set") + self.assertEqual(g(o), "sized") + c.Iterable.register(O) + self.assertEqual(g(o), "sized") + c.Container.register(O) + with assertRaises(RuntimeError): # was "sized" because in mro + self.assertEqual(g(o), "sized") + c.Set.register(O) + self.assertEqual(g(o), "set") + + class P(object): + pass + p = P() + self.assertEqual(g(p), "base") + c.Iterable.register(P) + self.assertEqual(g(p), "iterable") + c.Container.register(P) + + with assertRaises(RuntimeError): + self.assertEqual(g(p), "iterable") + + class Q(c.Sized): + def __len__(self): + return 0 + q = Q() + self.assertEqual(g(q), "sized") + c.Iterable.register(Q) + self.assertEqual(g(q), "sized") + c.Set.register(Q) + self.assertEqual(g(q), "set") + # because c.Set is a subclass of c.Sized and c.Iterable + + @singledispatch + def h(obj): + return "base" + + @h.register(c.Sized) + def h_sized(arg): + return "sized" + + @h.register(c.Container) + def h_container(arg): + return "container" + # Even though Sized and Container are explicit bases of MutableMapping, + # this ABC is implicitly registered on defaultdict which makes all of + # MutableMapping's bases implicit as well from defaultdict's + # perspective. + with assertRaises(RuntimeError): + self.assertEqual(h(defaultdict(lambda: 0)), "sized") + + class R(defaultdict): + pass + c.MutableSequence.register(R) + + @singledispatch + def i(obj): + return "base" + + @i.register(c.MutableMapping) + def i_mapping(arg): + return "mapping" + + @i.register(c.MutableSequence) + def i_sequence(arg): + return "sequence" + r = R() + with assertRaises(RuntimeError): # was no error + self.assertEqual(i(r), "sequence") + + class S(object): + pass + + class T(S, c.Sized): + def __len__(self): + return 0 + t = T() + self.assertEqual(h(t), "sized") + c.Container.register(T) + self.assertEqual(h(t), "sized") # because it's explicitly in the MRO + + class U(object): + def __len__(self): + return 0 + u = U() + self.assertEqual(h(u), "sized") + # implicit Sized subclass inferred + # from the existence of __len__() + + c.Container.register(U) + # There is no preference for registered versus inferred ABCs. + with assertRaises(RuntimeError): + h(u) + + +if __name__ == '__main__': + unittest.main() diff --git a/contrib/python/decorator/py2/tests/ya.make b/contrib/python/decorator/py2/tests/ya.make new file mode 100644 index 0000000000..17111c31a7 --- /dev/null +++ b/contrib/python/decorator/py2/tests/ya.make @@ -0,0 +1,19 @@ +PY2TEST() + +PEERDIR( + contrib/python/decorator +) + +PY_SRCS( + TOP_LEVEL + documentation.py +) + +TEST_SRCS( + #documentation.py + test.py +) + +NO_LINT() + +END() diff --git a/contrib/python/decorator/py2/ya.make b/contrib/python/decorator/py2/ya.make new file mode 100644 index 0000000000..69d2592df3 --- /dev/null +++ b/contrib/python/decorator/py2/ya.make @@ -0,0 +1,26 @@ +# Generated by devtools/yamaker (pypi). + +PY2_LIBRARY() + +VERSION(4.4.2) + +LICENSE(BSD-3-Clause) + +NO_LINT() + +PY_SRCS( + TOP_LEVEL + decorator.py +) + +RESOURCE_FILES( + PREFIX contrib/python/decorator/py2/ + .dist-info/METADATA + .dist-info/top_level.txt +) + +END() + +RECURSE_FOR_TESTS( + tests +) diff --git a/contrib/python/decorator/py3/.dist-info/METADATA b/contrib/python/decorator/py3/.dist-info/METADATA new file mode 100644 index 0000000000..fd12277a01 --- /dev/null +++ b/contrib/python/decorator/py3/.dist-info/METADATA @@ -0,0 +1,131 @@ +Metadata-Version: 2.1 +Name: decorator +Version: 4.4.2 +Summary: Decorators for Humans +Home-page: https://github.com/micheles/decorator +Author: Michele Simionato +Author-email: michele.simionato@gmail.com +License: new BSD License +Keywords: decorators generic utility +Platform: All +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Natural Language :: English +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.2 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Utilities +Requires-Python: >=2.6, !=3.0.*, !=3.1.* + +Decorators for Humans +===================== + +The goal of the decorator module is to make it easy to define +signature-preserving function decorators and decorator factories. +It also includes an implementation of multiple dispatch and other niceties +(please check the docs). It is released under a two-clauses +BSD license, i.e. basically you can do whatever you want with it but I am not +responsible. + +Installation +------------- + +If you are lazy, just perform + + ``$ pip install decorator`` + +which will install just the module on your system. + +If you prefer to install the full distribution from source, including +the documentation, clone the `GitHub repo`_ or download the tarball_, unpack it and run + + ``$ pip install .`` + +in the main directory, possibly as superuser. + +.. _tarball: https://pypi.org/project/decorator/#files +.. _GitHub repo: https://github.com/micheles/decorator + +Testing +-------- + +If you have the source code installation you can run the tests with + + `$ python src/tests/test.py -v` + +or (if you have setuptools installed) + + `$ python setup.py test` + +Notice that you may run into trouble if in your system there +is an older version of the decorator module; in such a case remove the +old version. It is safe even to copy the module `decorator.py` over +an existing one, since we kept backward-compatibility for a long time. + +Repository +--------------- + +The project is hosted on GitHub. You can look at the source here: + + https://github.com/micheles/decorator + +Documentation +--------------- + +The documentation has been moved to https://github.com/micheles/decorator/blob/master/docs/documentation.md + +From there you can get a PDF version by simply using the print +functionality of your browser. + +Here is the documentation for previous versions of the module: + +https://github.com/micheles/decorator/blob/4.3.2/docs/tests.documentation.rst +https://github.com/micheles/decorator/blob/4.2.1/docs/tests.documentation.rst +https://github.com/micheles/decorator/blob/4.1.2/docs/tests.documentation.rst +https://github.com/micheles/decorator/blob/4.0.0/documentation.rst +https://github.com/micheles/decorator/blob/3.4.2/documentation.rst + +For the impatient +----------------- + +Here is an example of how to define a family of decorators tracing slow +operations: + +.. code-block:: python + + from decorator import decorator + + @decorator + def warn_slow(func, timelimit=60, *args, **kw): + t0 = time.time() + result = func(*args, **kw) + dt = time.time() - t0 + if dt > timelimit: + logging.warn('%s took %d seconds', func.__name__, dt) + else: + logging.info('%s took %d seconds', func.__name__, dt) + return result + + @warn_slow # warn if it takes more than 1 minute + def preprocess_input_files(inputdir, tempdir): + ... + + @warn_slow(timelimit=600) # warn if it takes more than 10 minutes + def run_calculation(tempdir, outdir): + ... + +Enjoy! + + diff --git a/contrib/python/decorator/py3/.dist-info/top_level.txt b/contrib/python/decorator/py3/.dist-info/top_level.txt new file mode 100644 index 0000000000..3fe18a4d1c --- /dev/null +++ b/contrib/python/decorator/py3/.dist-info/top_level.txt @@ -0,0 +1 @@ +decorator diff --git a/contrib/python/decorator/py3/LICENSE.txt b/contrib/python/decorator/py3/LICENSE.txt new file mode 100644 index 0000000000..b0ade0487e --- /dev/null +++ b/contrib/python/decorator/py3/LICENSE.txt @@ -0,0 +1,26 @@ +Copyright (c) 2005-2018, 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. diff --git a/contrib/python/decorator/py3/README.rst b/contrib/python/decorator/py3/README.rst new file mode 100644 index 0000000000..f56161c74a --- /dev/null +++ b/contrib/python/decorator/py3/README.rst @@ -0,0 +1,98 @@ +Decorators for Humans +===================== + +The goal of the decorator module is to make it easy to define +signature-preserving function decorators and decorator factories. +It also includes an implementation of multiple dispatch and other niceties +(please check the docs). It is released under a two-clauses +BSD license, i.e. basically you can do whatever you want with it but I am not +responsible. + +Installation +------------- + +If you are lazy, just perform + + ``$ pip install decorator`` + +which will install just the module on your system. + +If you prefer to install the full distribution from source, including +the documentation, clone the `GitHub repo`_ or download the tarball_, unpack it and run + + ``$ pip install .`` + +in the main directory, possibly as superuser. + +.. _tarball: https://pypi.org/project/decorator/#files +.. _GitHub repo: https://github.com/micheles/decorator + +Testing +-------- + +If you have the source code installation you can run the tests with + + `$ python src/tests/test.py -v` + +or (if you have setuptools installed) + + `$ python setup.py test` + +Notice that you may run into trouble if in your system there +is an older version of the decorator module; in such a case remove the +old version. It is safe even to copy the module `decorator.py` over +an existing one, since we kept backward-compatibility for a long time. + +Repository +--------------- + +The project is hosted on GitHub. You can look at the source here: + + https://github.com/micheles/decorator + +Documentation +--------------- + +The documentation has been moved to https://github.com/micheles/decorator/blob/master/docs/documentation.md + +From there you can get a PDF version by simply using the print +functionality of your browser. + +Here is the documentation for previous versions of the module: + +https://github.com/micheles/decorator/blob/4.3.2/docs/tests.documentation.rst +https://github.com/micheles/decorator/blob/4.2.1/docs/tests.documentation.rst +https://github.com/micheles/decorator/blob/4.1.2/docs/tests.documentation.rst +https://github.com/micheles/decorator/blob/4.0.0/documentation.rst +https://github.com/micheles/decorator/blob/3.4.2/documentation.rst + +For the impatient +----------------- + +Here is an example of how to define a family of decorators tracing slow +operations: + +.. code-block:: python + + from decorator import decorator + + @decorator + def warn_slow(func, timelimit=60, *args, **kw): + t0 = time.time() + result = func(*args, **kw) + dt = time.time() - t0 + if dt > timelimit: + logging.warn('%s took %d seconds', func.__name__, dt) + else: + logging.info('%s took %d seconds', func.__name__, dt) + return result + + @warn_slow # warn if it takes more than 1 minute + def preprocess_input_files(inputdir, tempdir): + ... + + @warn_slow(timelimit=600) # warn if it takes more than 10 minutes + def run_calculation(tempdir, outdir): + ... + +Enjoy! diff --git a/contrib/python/decorator/py3/decorator.py b/contrib/python/decorator/py3/decorator.py new file mode 100644 index 0000000000..b1f8b567e9 --- /dev/null +++ b/contrib/python/decorator/py3/decorator.py @@ -0,0 +1,454 @@ +# ######################### LICENSE ############################ # + +# Copyright (c) 2005-2018, 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 + +import re +import sys +import inspect +import operator +import itertools +import collections + +__version__ = '4.4.2' + +if sys.version_info >= (3,): + from inspect import getfullargspec + + def get_init(cls): + return cls.__init__ +else: + FullArgSpec = collections.namedtuple( + 'FullArgSpec', 'args varargs varkw defaults ' + 'kwonlyargs kwonlydefaults annotations') + + def getfullargspec(f): + "A quick and dirty replacement for getfullargspec for Python 2.X" + return FullArgSpec._make(inspect.getargspec(f) + ([], None, {})) + + def get_init(cls): + return cls.__init__.__func__ + +try: + iscoroutinefunction = inspect.iscoroutinefunction +except AttributeError: + # let's assume there are no coroutine functions in old Python + def iscoroutinefunction(f): + return False +try: + from inspect import isgeneratorfunction +except ImportError: + # assume no generator function in old Python versions + def isgeneratorfunction(caller): + return False + + +DEF = re.compile(r'\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() + + # make pylint happy + args = varargs = varkw = defaults = kwonlyargs = kwonlydefaults = () + + def __init__(self, func=None, name=None, signature=None, + defaults=None, doc=None, module=None, funcdict=None): + self.shortsignature = signature + if func: + # func can be a class or a callable, but not an instance method + self.name = func.__name__ + if self.name == '<lambda>': # small hack for lambda functions + self.name = '_lambda_' + self.doc = func.__doc__ + 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'): + setattr(self, a, getattr(argspec, a)) + for i, arg in enumerate(self.args): + setattr(self, 'arg%d' % i, arg) + 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: + self.name = name + if signature is not None: + self.signature = signature + if defaults: + self.defaults = defaults + if doc: + self.doc = doc + if module: + self.module = module + if funcdict: + self.dict = funcdict + # check existence required attributes + assert hasattr(self, 'name') + if not hasattr(self, 'signature'): + raise TypeError('You are decorating a non function: %s' % func) + + def update(self, func, **kw): + "Update the signature of func with the data in self" + func.__name__ = self.name + func.__doc__ = getattr(self, 'doc', None) + func.__dict__ = getattr(self, 'dict', {}) + func.__defaults__ = self.defaults + func.__kwdefaults__ = self.kwonlydefaults or None + 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) + + def make(self, src_templ, evaldict=None, addsource=False, **attrs): + "Make a new function from a given template and update the signature" + src = src_templ % vars(self) # expand name and signature + evaldict = evaldict or {} + mo = DEF.search(src) + if mo is None: + 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(',')]) + 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 for old Pythons + src += '\n' + + # Ensure each generated function has a unique filename for profilers + # (such as cProfile) that depend on the tuple of (<filename>, + # <definition line>, <function name>) being unique. + filename = '<decorator-gen-%d>' % next(self._compile_count) + try: + code = compile(src, filename, 'single') + exec(code, evaldict) + except Exception: + print('Error in generated code:', file=sys.stderr) + print(src, file=sys.stderr) + raise + func = evaldict[name] + if addsource: + attrs['__source__'] = src + self.update(func, **attrs) + return func + + @classmethod + def create(cls, obj, body, evaldict, defaults=None, + 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. + """ + if isinstance(obj, str): # "name(signature)" + name, rest = obj.strip().split('(', 1) + signature = rest[:-1] # strip a right parens + func = None + else: # a function + name = None + signature = None + func = obj + self = cls(func, name, signature, defaults, doc, module) + ibody = '\n'.join(' ' + line for line in body.splitlines()) + caller = evaldict.get('_call_') # when called from `decorate` + if caller and iscoroutinefunction(caller): + body = ('async def %(name)s(%(signature)s):\n' + ibody).replace( + 'return', 'return await') + else: + body = 'def %(name)s(%(signature)s):\n' + ibody + return self.make(body, evaldict, addsource, **attrs) + + +def decorate(func, caller, extras=()): + """ + decorate(func, caller) decorates a function using a caller. + If the caller is a generator function, the resulting function + will be a generator function. + """ + evaldict = dict(_call_=caller, _func_=func) + es = '' + for i, extra in enumerate(extras): + ex = '_e%d_' % i + evaldict[ex] = extra + es += ex + ', ' + + if '3.5' <= sys.version < '3.6': + # with Python 3.5 isgeneratorfunction returns True for all coroutines + # however we know that it is NOT possible to have a generator + # coroutine in python 3.5: PEP525 was not there yet + generatorcaller = isgeneratorfunction( + caller) and not iscoroutinefunction(caller) + else: + generatorcaller = isgeneratorfunction(caller) + if generatorcaller: + fun = FunctionMaker.create( + func, "for res in _call_(_func_, %s%%(shortsignature)s):\n" + " yield res" % es, evaldict, __wrapped__=func) + else: + fun = FunctionMaker.create( + func, "return _call_(_func_, %s%%(shortsignature)s)" % es, + 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 + defaultargs, defaults = '', () + if inspect.isclass(caller): + name = caller.__name__.lower() + doc = 'decorator(%s) converts functions/generators into ' \ + 'factories of %s objects' % (caller.__name__, caller.__name__) + elif inspect.isfunction(caller): + if caller.__name__ == '<lambda>': + name = '_lambda_' + else: + name = caller.__name__ + doc = caller.__doc__ + nargs = caller.__code__.co_argcount + ndefs = len(caller.__defaults__ or ()) + defaultargs = ', '.join(caller.__code__.co_varnames[nargs-ndefs:nargs]) + if defaultargs: + defaultargs += ',' + defaults = caller.__defaults__ + else: # assume caller is an object with a __call__ method + name = caller.__class__.__name__.lower() + doc = caller.__call__.__doc__ + evaldict = dict(_call=caller, _decorate_=decorate) + dec = FunctionMaker.create( + '%s(func, %s)' % (name, defaultargs), + 'if func is None: return lambda func: _decorate_(func, _call, (%s))\n' + 'return _decorate_(func, _call, (%s))' % (defaultargs, defaultargs), + evaldict, doc=doc, module=caller.__module__, __wrapped__=caller) + if defaults: + dec.__defaults__ = (None,) + defaults + return dec + + +# ####################### 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, "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) + + +def contextmanager(func): + # Enable Pylint config: contextmanager-decorators=decorator.contextmanager + return _contextmanager(func) + + +# ############################ 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( + 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 diff --git a/contrib/python/decorator/py3/tests/__init__.py b/contrib/python/decorator/py3/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/contrib/python/decorator/py3/tests/__init__.py diff --git a/contrib/python/decorator/py3/tests/documentation.py b/contrib/python/decorator/py3/tests/documentation.py new file mode 100644 index 0000000000..46a932aa77 --- /dev/null +++ b/contrib/python/decorator/py3/tests/documentation.py @@ -0,0 +1,1897 @@ +from __future__ import print_function +import sys +import threading +import time +import functools +import itertools +import collections +try: + import collections.abc as c +except ImportError: + c = collections + collections.abc = collections +from decorator import (decorator, decorate, FunctionMaker, contextmanager, + dispatch_on, __version__) + +doc = r"""Decorators for Humans +---------------------------------- + +|Author | Michele Simionato| +|---|---| +|E-mail | michele.simionato@gmail.com| +|Version| $VERSION ($DATE)| +|Supports| Python 2.6, 2.7, 3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8| +|Download page| http://pypi.python.org/pypi/decorator/$VERSION| +|Installation| ``pip install decorator``| +|License | BSD license| + +Introduction +----------------------------------------- + +The ``decorator`` module is over ten years old, but still alive and +kicking. It is used by several frameworks (IPython, scipy, authkit, +pylons, pycuda, sugar, ...) and has been stable for a *long* +time. It is your best option if you want to preserve the signature of +decorated functions in a consistent way across Python +releases. Version 4 is fully compatible with the past, except for +one thing: support for Python 2.4 and 2.5 has been dropped. That +decision made it possible to use a single code base both for Python +2.X and Python 3.X. This is a *huge* bonus, since I could remove over +2,000 lines of duplicated documentation/doctests. Having to maintain +separate docs for Python 2 and Python 3 effectively stopped any +development on the module for several years. Moreover, it is now +trivial to distribute the module as an universal + [wheel](http://pythonwheels.com) since 2to3 is no more +required. Since Python 2.5 has been released ages ago (in 2006), I felt that +it was reasonable to drop the support for it. If you need to support +ancient versions of Python, stick with the decorator module version +3.4.2. The current version supports all Python releases from 2.6 up. + +What's New in version 4 +----------------------- + +- **New documentation** + There is now a single manual for all Python versions, so I took the + opportunity to overhaul the documentation and to move it to readthedocs.org. + Even if you are a long-time user, you may want to revisit the docs, since + several examples have been improved. + +- **Packaging improvements** + The code is now also available in wheel format. Integration with + setuptools has improved and you can run the tests with the command + ``python setup.py test`` too. + +- **Code changes** + A new utility function ``decorate(func, caller)`` has been added. + It does the same job that was performed by the older + ``decorator(caller, func)``. The old functionality is now deprecated + and no longer documented, but still available for now. + +- **Multiple dispatch** + The decorator module now includes an implementation of generic + functions (sometimes called "multiple dispatch functions"). + The API is designed to mimic ``functools.singledispatch`` (added + in Python 3.4), but the implementation is much simpler. + Moreover, all decorators involved preserve the signature of the + decorated functions. For now, this exists mostly to demonstrate + the power of the module. In the future it could be enhanced/optimized. + In any case, it is very short and compact (less then 100 lines), so you + can extract it for your own use. Take it as food for thought. + +- **Python 3.5 coroutines** + From version 4.1 it is possible to decorate coroutines, i.e. functions + defined with the `async def` syntax, and to maintain the + `inspect.iscoroutinefunction` check working for the decorated function. + +- **Decorator factories** + From version 4.2 there is facility to define factories of decorators in + a simple way, a feature requested by the users since a long time. + +Usefulness of decorators +------------------------------------------------ + +Python decorators are an interesting example of why syntactic sugar +matters. In principle, their introduction in Python 2.4 changed +nothing, since they did not provide any new functionality which was not +already present in the language. In practice, their introduction has +significantly changed the way we structure our programs in Python. I +believe the change is for the best, and that decorators are a great +idea since: + +* decorators help reducing boilerplate code; +* decorators help separation of concerns; +* decorators enhance readability and maintenability; +* decorators are explicit. + +Still, as of now, writing custom decorators correctly requires +some experience and it is not as easy as it could be. For instance, +typical implementations of decorators involve nested functions, and +we all know that flat is better than nested. + +The aim of the ``decorator`` module it to simplify the usage of +decorators for the average programmer, and to popularize decorators by +showing various non-trivial examples. Of course, as all techniques, +decorators can be abused (I have seen that) and you should not try to +solve every problem with a decorator, just because you can. + +You may find the source code for all the examples +discussed here in the ``documentation.py`` file, which contains +the documentation you are reading in the form of doctests. + +Definitions +------------------------------------ + +Technically speaking, any Python object which can be called with one argument +can be used as a decorator. However, this definition is somewhat too large +to be really useful. It is more convenient to split the generic class of +decorators in two subclasses: + +1. **signature-preserving decorators**, callable objects which accept + a function as input and return a function as output, *with the + same signature* + +2. **signature-changing** decorators, i.e. decorators + which change the signature of their input function, or decorators + that return non-callable objects + +Signature-changing decorators have their use: for instance, the +builtin classes ``staticmethod`` and ``classmethod`` are in this +group. They take functions and return descriptor objects which +are neither functions, nor callables. + +Still, signature-preserving decorators are more common, and easier +to reason about. In particular, they can be composed together, +whereas other decorators generally cannot. + +Writing signature-preserving decorators from scratch is not that +obvious, especially if one wants to define proper decorators that +can accept functions with any signature. A simple example will clarify +the issue. + +Statement of the problem +------------------------------ + +A very common use case for decorators is the memoization of functions. +A ``memoize`` decorator works by caching +the result of the function call in a dictionary, so that the next time +the function is called with the same input parameters the result is retrieved +from the cache and not recomputed. + +There are many implementations of ``memoize`` in +http://www.python.org/moin/PythonDecoratorLibrary, +but they do not preserve the signature. In recent versions of +Python you can find a sophisticated ``lru_cache`` decorator +in the standard library's ``functools``. Here I am just +interested in giving an example. + +Consider the following simple implementation (note that it is +generally impossible to *correctly* memoize something +that depends on non-hashable arguments): + +$$memoize_uw + +Here I used the functools.update_wrapper_ utility, which was added +in Python 2.5 to simplify the writing of decorators. +(Previously, you needed to manually copy the function attributes +``__name__``, ``__doc__``, ``__module__``, and ``__dict__`` +to the decorated function by hand). + +Here is an example of usage: + +$$f1 + +This works insofar as the decorator accepts functions with generic signatures. +Unfortunately, it is *not* a signature-preserving decorator, since +``memoize_uw`` generally returns a function with a *different signature* +from the original. + +Consider for instance the following case: + +$$f1 + +Here, the original function takes a single argument named ``x``, +but the decorated function takes any number of arguments and +keyword arguments: + +```python +>>> from decorator import getfullargspec +>>> print(getfullargspec(f1)) +FullArgSpec(args=[], varargs='args', varkw='kw', defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={}) + +``` + +This means that introspection tools (like ``pydoc``) will give false +information about the signature of ``f1`` -- unless you are using +Python 3.5. This is pretty bad: ``pydoc`` will tell you that the +function accepts the generic signature ``*args, **kw``, but +calling the function with more than one argument raises an error: + +```python +>>> f1(0, 1) # doctest: +IGNORE_EXCEPTION_DETAIL +Traceback (most recent call last): + ... +TypeError: f1() takes exactly 1 positional argument (2 given) + +``` + +Notice that ``inspect.getfullargspec`` +will give the wrong signature, even in the latest Python, i.e. version 3.6 +at the time of writing. + +The solution +----------------------------------------- + +The solution is to provide a generic factory of generators, which +hides the complexity of making signature-preserving decorators +from the application programmer. The ``decorate`` function in +the ``decorator`` module is such a factory: + +```python +>>> from decorator import decorate + +``` + +``decorate`` takes two arguments: + +1. a caller function describing the functionality of the decorator, and + +2. a function to be decorated. + +The caller function must have signature ``(f, *args, **kw)``, and it +must call the original function ``f`` with arguments ``args`` and ``kw``, +implementing the wanted capability (in this case, memoization): + +$$_memoize + +Now, you can define your decorator as follows: + +$$memoize + +The difference from the nested function approach of ``memoize_uw`` +is that the decorator module forces you to lift the inner function +to the outer level. Moreover, you are forced to explicitly pass the +function you want to decorate; there are no closures. + +Here is a test of usage: + +```python +>>> @memoize +... def heavy_computation(): +... time.sleep(2) +... return "done" + +>>> print(heavy_computation()) # the first time it will take 2 seconds +done + +>>> print(heavy_computation()) # the second time it will be instantaneous +done + +``` + +The signature of ``heavy_computation`` is the one you would expect: + +```python +>>> print(getfullargspec(heavy_computation)) +FullArgSpec(args=[], varargs=None, varkw=None, defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={}) + +``` + +A ``trace`` decorator +------------------------------------------------------ + +Here is an example of how to define a simple ``trace`` decorator, +which prints a message whenever the traced function is called: + +$$_trace + +$$trace + +Here is an example of usage: + +```python +>>> @trace +... def f1(x): +... pass + +``` + +It is immediate to verify that ``f1`` works... + +```python +>>> f1(0) +calling f1 with args (0,), {} + +``` + +...and it that it has the correct signature: + +```python +>>> print(getfullargspec(f1)) +FullArgSpec(args=['x'], varargs=None, varkw=None, defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={}) + +``` + +The decorator works with functions of any signature: + +```python +>>> @trace +... def f(x, y=1, z=2, *args, **kw): +... pass + +>>> f(0, 3) +calling f with args (0, 3, 2), {} + +>>> print(getfullargspec(f)) +FullArgSpec(args=['x', 'y', 'z'], varargs='args', varkw='kw', defaults=(1, 2), kwonlyargs=[], kwonlydefaults=None, annotations={}) + +``` +$FUNCTION_ANNOTATIONS + +``decorator.decorator`` +--------------------------------------------- + +It can become tedious to write a caller function (like the above +``_trace`` example) and then a trivial wrapper +(``def trace(f): return decorate(f, _trace)``) every time. +Not to worry! The ``decorator`` module provides an easy shortcut +to convert the caller function into a signature-preserving decorator. + +It is the ``decorator`` function: + +```python +>>> from decorator import decorator +>>> print(decorator.__doc__) +decorator(caller) converts a caller function into a decorator + +``` +The ``decorator`` function can be used as a signature-changing +decorator, just like ``classmethod`` and ``staticmethod``. +But ``classmethod`` and ``staticmethod`` return generic +objects which are not callable. Instead, ``decorator`` returns +signature-preserving decorators (i.e. functions with a single argument). + +For instance, you can write: + +```python +>>> @decorator +... def trace(f, *args, **kw): +... kwstr = ', '.join('%r: %r' % (k, kw[k]) for k in sorted(kw)) +... print("calling %s with args %s, {%s}" % (f.__name__, args, kwstr)) +... return f(*args, **kw) + +``` + +And ``trace`` is now a decorator! + +```python +>>> trace # doctest: +ELLIPSIS +<function trace at 0x...> + +``` + +Here is an example of usage: + +```python +>>> @trace +... def func(): pass + +>>> func() +calling func with args (), {} + +``` + +The `decorator` function can also be used to define factories of decorators, +i.e. functions returning decorators. In general you can just write something +like this: + +```python +def decfactory(param1, param2, ...): + def caller(f, *args, **kw): + return somefunc(f, param1, param2, .., *args, **kw) + return decorator(caller) +``` + +This is fully general but requires an additional level of nesting. For this +reason since version 4.2 there is a facility to build +decorator factories by using a single caller with default arguments i.e. +writing something like this: + +```python +def caller(f, param1=default1, param2=default2, ..., *args, **kw): + return somefunc(f, param1, param2, *args, **kw) +decfactory = decorator(caller) +``` + +Notice that this simplified approach *only works with default arguments*, +i.e. `param1`, `param2` etc must have known defaults. Thanks to this +restriction, there exists an unique default decorator, i.e. the member +of the family which uses the default values for all parameters. Such +decorator can be written as ``decfactory()`` with no parameters specified; +moreover, as a shortcut, it is also possible to elide the parenthesis, +a feature much requested by the users. For years I have been opposite +to this feature request, since having explicit parenthesis to me is more clear +and less magic; however once this feature entered in decorators of +the Python standard library (I am referring to the [dataclass decorator]( +https://www.python.org/dev/peps/pep-0557/)) I finally gave up. + +The example below will show how it works in practice. + +Decorator factories +------------------------------------------- + +Sometimes one has to deal with blocking resources, such as ``stdin``. +Sometimes it is better to receive a "busy" message than just blocking +everything. +This can be accomplished with a suitable family of decorators (decorator +factory), parameterize by a string, the busy message: + +$$blocking + +Functions decorated with ``blocking`` will return a busy message if +the resource is unavailable, and the intended result if the resource is +available. For instance: + +```python +>>> @blocking(msg="Please wait ...") +... def read_data(): +... time.sleep(3) # simulate a blocking resource +... return "some data" + +>>> print(read_data()) # data is not available yet +Please wait ... + +>>> time.sleep(1) +>>> print(read_data()) # data is not available yet +Please wait ... + +>>> time.sleep(1) +>>> print(read_data()) # data is not available yet +Please wait ... + +>>> time.sleep(1.1) # after 3.1 seconds, data is available +>>> print(read_data()) +some data + +``` + +Decorator factories are most useful to framework builders. Here is an example +that gives an idea of how you could manage permissions in a framework: + +$$Action + +where ``restricted`` is a decorator factory defined as follows + +$$restricted + +Notice that if you forget to use the keyword argument notation, i.e. if you +write ``restricted(User)`` instead of ``restricted(user_class=User)`` you +will get an error + +```python +TypeError: You are decorating a non function: <class '__main__.User'> + +``` + +Be careful! + +``decorator(cls)`` +-------------------------------------------- + +The ``decorator`` facility can also produce a decorator starting +from a class with the signature of a caller. In such a case the +produced generator is able to convert functions into factories +to create instances of that class. + +As an example, here is a decorator which can convert a +blocking function into an asynchronous function. When +the function is called, it is executed in a separate thread. + +(This is similar to the approach used in the ``concurrent.futures`` package. +But I don't recommend that you implement futures this way; this is just an +example.) + +$$Future + +The decorated function returns a ``Future`` object. It has a ``.result()`` +method which blocks until the underlying thread finishes and returns +the final result. + +Here is the minimalistic usage: + +```python +>>> @decorator(Future) +... def long_running(x): +... time.sleep(.5) +... return x + +>>> fut1 = long_running(1) +>>> fut2 = long_running(2) +>>> fut1.result() + fut2.result() +3 + +``` + +contextmanager +------------------------------------- + +Python's standard library has the ``contextmanager`` decorator, +which converts a generator function into a ``GeneratorContextManager`` +factory. For instance, if you write this... + +```python +>>> from contextlib import contextmanager +>>> @contextmanager +... def before_after(before, after): +... print(before) +... yield +... print(after) + +``` + +...then ``before_after`` is a factory function that returns +``GeneratorContextManager`` objects, which provide the +use of the ``with`` statement: + +```python +>>> with before_after('BEFORE', 'AFTER'): +... print('hello') +BEFORE +hello +AFTER + +``` + +Basically, it is as if the content of the ``with`` block was executed +in the place of the ``yield`` expression in the generator function. + +In Python 3.2, ``GeneratorContextManager`` objects were enhanced with +a ``__call__`` method, so that they can be used as decorators, like so: + +```python +>>> ba = before_after('BEFORE', 'AFTER') +>>> +>>> @ba # doctest: +SKIP +... def hello(): +... print('hello') +... +>>> hello() # doctest: +SKIP +BEFORE +hello +AFTER + +``` + +The ``ba`` decorator basically inserts a ``with ba:`` block +inside the function. + +However, there are two issues: + +1. ``GeneratorContextManager`` objects are only callable in Python 3.2, + so the previous example breaks in older versions of Python. + (You can solve this by installing ``contextlib2``, which backports + the Python 3 functionality to Python 2.) + +2. ``GeneratorContextManager`` objects do not preserve the signature of + the decorated functions. The decorated ``hello`` function above will + have the generic signature ``hello(*args, **kwargs)``, but fails if + called with more than zero arguments. + +For these reasons, the `decorator` module, starting from release 3.4, offers a +``decorator.contextmanager`` decorator that solves both problems, +*and* works in all supported Python versions. Its usage is identical, +and factories decorated with ``decorator.contextmanager`` will return +instances of ``ContextManager``, a subclass of the standard library's +``contextlib.GeneratorContextManager`` class. The subclass includes +an improved ``__call__`` method, which acts as a signature-preserving +decorator. + +The ``FunctionMaker`` class +--------------------------------------------------------------- + +You may wonder how the functionality of the ``decorator`` module +is implemented. The basic building block is +a ``FunctionMaker`` class. It generates on-the-fly functions +with a given name and signature from a function template +passed as a string. + +If you're just writing ordinary decorators, then you probably won't +need to use ``FunctionMaker`` directly. But in some circumstances, it +can be handy. You will see an example shortly--in +the implementation of a cool decorator utility (``decorator_apply``). + +``FunctionMaker`` provides the ``.create`` classmethod, which +accepts the *name*, *signature*, and *body* of the function +you want to generate, as well as the execution environment +where the function is generated by ``exec``. + +Here's an example: + +```python +>>> def f(*args, **kw): # a function with a generic signature +... print(args, kw) + +>>> f1 = FunctionMaker.create('f1(a, b)', 'f(a, b)', dict(f=f)) +>>> f1(1,2) +(1, 2) {} + +``` + +It is important to notice that the function body is interpolated +before being executed; **be careful** with the ``%`` sign! + +``FunctionMaker.create`` also accepts keyword arguments. +The keyword arguments are attached to the generated function. +This is useful if you want to set some function attributes +(e.g., the docstring ``__doc__``). + +For debugging/introspection purposes, it may be useful to see +the source code of the generated function. To do this, just +pass ``addsource=True``, and the generated function will get +a ``__source__`` attribute: + +```python +>>> f1 = FunctionMaker.create( +... 'f1(a, b)', 'f(a, b)', dict(f=f), addsource=True) +>>> print(f1.__source__) +def f1(a, b): + f(a, b) +<BLANKLINE> + +``` + +The first argument to ``FunctionMaker.create`` can be a string (as above), +or a function. This is the most common usage, since you typically decorate +pre-existing functions. + +If you're writing a framework, however, you may want to use +``FunctionMaker.create`` directly, rather than ``decorator``, because it gives +you direct access to the body of the generated function. + +For instance, suppose you want to instrument the ``__init__`` methods of a +set of classes, by preserving their signature. +(This use case is not made up. This is done by SQAlchemy, and other frameworks, +too.) +Here is what happens: + +- If first argument of ``FunctionMaker.create`` is a function, + an instance of ``FunctionMaker`` is created with the attributes + ``args``, ``varargs``, ``keywords``, and ``defaults``. + (These mirror the return values of the standard library's + ``inspect.getfullargspec``.) + +- For each item in ``args`` (a list of strings of the names of all required + arguments), an attribute ``arg0``, ``arg1``, ..., ``argN`` is also generated. + +- Finally, there is a ``signature`` attribute, which is a string with the + signature of the original function. + +**NOTE:** You should not pass signature strings with default arguments +(e.g., something like ``'f1(a, b=None)'``). Just pass ``'f1(a, b)'``, +followed by a tuple of defaults: + +```python +>>> f1 = FunctionMaker.create( +... 'f1(a, b)', 'f(a, b)', dict(f=f), addsource=True, defaults=(None,)) +>>> print(getfullargspec(f1)) +FullArgSpec(args=['a', 'b'], varargs=None, varkw=None, defaults=(None,), kwonlyargs=[], kwonlydefaults=None, annotations={}) + +``` + +Getting the source code +--------------------------------------------------- + +Internally, ``FunctionMaker.create`` uses ``exec`` to generate the +decorated function. Therefore ``inspect.getsource`` will not work for +decorated functions. In IPython, this means that the usual ``??`` trick +will give you the (right on the spot) message ``Dynamically generated +function. No source code available``. + +In the past, I considered this acceptable, since ``inspect.getsource`` +does not really work with "regular" decorators. In those cases, +``inspect.getsource`` gives you the wrapper source code, which is probably +not what you want: + +$$identity_dec +$$example + +```python +>>> import inspect +>>> print(inspect.getsource(example)) + def wrapper(*args, **kw): + return func(*args, **kw) +<BLANKLINE> + +``` + +(See bug report [1764286](http://bugs.python.org/issue1764286) +for an explanation of what is happening). +Unfortunately the bug still exists in all versions of Python < 3.5. + +However, there is a workaround. The decorated function has the ``__wrapped__`` +attribute, pointing to the original function. The simplest way to get the +source code is to call ``inspect.getsource`` on the undecorated function: + +```python +>>> print(inspect.getsource(factorial.__wrapped__)) +@tail_recursive +def factorial(n, acc=1): + "The good old factorial" + if n == 0: + return acc + return factorial(n-1, n*acc) +<BLANKLINE> + +``` + +Dealing with third-party decorators +----------------------------------------------------------------- + +Sometimes on the net you find some cool decorator that you would +like to include in your code. However, more often than not, the cool +decorator is not signature-preserving. What you need is an easy way to +upgrade third party decorators to signature-preserving decorators... +*without* having to rewrite them in terms of ``decorator``. + +You can use a ``FunctionMaker`` to implement that functionality as follows: + +$$decorator_apply + +``decorator_apply`` sets the generated function's ``__wrapped__`` attribute +to the original function, so you can get the right source code. +If you are using a Python later than 3.2, you should also set the +``__qualname__`` attribute to preserve the qualified name of the original +function. + +Notice that I am not providing this functionality in the ``decorator`` +module directly, since I think it is best to rewrite the decorator instead +of adding another level of indirection. However, practicality +beats purity, so you can add ``decorator_apply`` to your toolbox and +use it if you need to. + +To give a good example for ``decorator_apply``, I will show a pretty slick +decorator that converts a tail-recursive function into an iterative function. +I have shamelessly stolen the core concept from Kay Schluehr's recipe +in the Python Cookbook, +http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496691. + +$$TailRecursive + +Here the decorator is implemented as a class returning callable +objects. + +$$tail_recursive + +Here is how you apply the upgraded decorator to the good old factorial: + +$$factorial + +```python +>>> print(factorial(4)) +24 + +``` + +This decorator is pretty impressive, and should give you some food for +thought! ;) + +Notice that there is no recursion limit now; you can easily compute +``factorial(1001)`` (or larger) without filling the stack frame. + +Notice also that the decorator will *not* work on functions which +are not tail recursive, such as the following: + +$$fact + +**Reminder:** A function is *tail recursive* if it does either of the +following: + +- returns a value without making a recursive call; or, +- returns directly the result of a recursive call. + +Python 3.5 coroutines +----------------------- + +I am personally not using Python 3.5 coroutines yet, because at work we are +still maintaining compatibility with Python 2.7. However, some users requested +support for coroutines and since version 4.1 the decorator module has it. +You should consider the support experimental and kindly report issues if +you find any. + +Here I will give a single example of usage. Suppose you want to log the moment +a coroutine starts and the moment it stops for debugging purposes. You could +write code like the following: + +```python +import time +import logging +from asyncio import get_event_loop, sleep, wait +from decorator import decorator + + @decorator +async def log_start_stop(coro, *args, **kwargs): + logging.info('Starting %s%s', coro.__name__, args) + t0 = time.time() + await coro(*args, **kwargs) + dt = time.time() - t0 + logging.info('Ending %s%s after %d seconds', coro.__name__, args, dt) + +@log_start_stop +async def make_task(n): + for i in range(n): + await sleep(1) + +if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + tasks = [make_task(3), make_task(2), make_task(1)] + get_event_loop().run_until_complete(wait(tasks)) +``` + +and you will get an output like this: + +```bash +INFO:root:Starting make_task(1,) +INFO:root:Starting make_task(3,) +INFO:root:Starting make_task(2,) +INFO:root:Ending make_task(1,) after 1 seconds +INFO:root:Ending make_task(2,) after 2 seconds +INFO:root:Ending make_task(3,) after 3 seconds +``` + +This may be handy if you have trouble understanding what it going on +with a particularly complex chain of coroutines. With a single line you +can decorate the troubling coroutine function, understand what happens, fix the +issue and then remove the decorator (or keep it if continuous monitoring +of the coroutines makes sense). Notice that +``inspect.iscoroutinefunction(make_task)`` +will return the right answer (i.e. ``True``). + +It is also possible to define decorators converting coroutine functions +into regular functions, such as the following: + +```python +@decorator +def coro_to_func(coro, *args, **kw): + "Convert a coroutine into a function" + return get_event_loop().run_until_complete(coro(*args, **kw)) +``` + +Notice the diffence: the caller in ``log_start_stop`` was a coroutine +function and the associate decorator was converting coroutines->coroutines; +the caller in ``coro_to_func`` is a regular function and converts +coroutines -> functions. + +Multiple dispatch +------------------------------------------- + +There has been talk of implementing multiple dispatch functions +(i.e. "generic functions") in Python for over ten years. Last year, +something concrete was done for the first time. As of Python 3.4, +we have the decorator ``functools.singledispatch`` to implement generic +functions! + +As its name implies, it is limited to *single dispatch*; in other words, +it is able to dispatch on the first argument of the function only. + +The ``decorator`` module provides the decorator factory ``dispatch_on``, +which can be used to implement generic functions dispatching on *any* argument. +Moreover, it can manage dispatching on more than one argument. +(And, of course, it is signature-preserving.) + +Here is a concrete example (from a real-life use case) where it is desiderable +to dispatch on the second argument. + +Suppose you have an ``XMLWriter`` class, which is instantiated +with some configuration parameters, and has the ``.write`` method which +serializes objects to XML: + +$$XMLWriter + +Here, you want to dispatch on the *second* argument; the first is already +taken by ``self``. The ``dispatch_on`` decorator factory allows you to specify +the dispatch argument simply by passing its name as a string. (Note +that if you misspell the name you will get an error.) + +The decorated function `write` is turned into a generic function ( +`write` is a function at the idea it is decorated; it will be turned +into a method later, at class instantiation time), +and it is called if there are no more specialized implementations. + +Usually, default functions should raise a ``NotImplementedError``, thus +forcing people to register some implementation. +You can perform the registration with a decorator: + +$$writefloat + +Now ``XMLWriter`` can serialize floats: + +```python +>>> writer = XMLWriter() +>>> writer.write(2.3) +'<float>2.3</float>' + +``` + +I could give a down-to-earth example of situations in which it is desiderable +to dispatch on more than one argument--for instance, I once implemented +a database-access library where the first dispatching argument was the +the database driver, and the second was the database record--but here +I will follow tradition, and show the time-honored Rock-Paper-Scissors example: + +$$Rock +$$Paper +$$Scissors + +I have added an ordinal to the Rock-Paper-Scissors classes to simplify +the implementation. The idea is to define a generic function (``win(a, +b)``) of two arguments corresponding to the *moves* of the first and +second players. The *moves* are instances of the classes +Rock, Paper, and Scissors: + +- Paper wins over Rock +- Scissors wins over Paper +- Rock wins over Scissors + +The function will return +1 for a win, -1 for a loss, and 0 for parity. +There are 9 combinations, but combinations with the same ordinal +(i.e. the same class) return 0. Moreover, by exchanging the order of the +arguments, the sign of the result changes. Therefore, it is sufficient to +directly specify only 3 implementations: + +$$win +$$winRockPaper +$$winPaperScissors +$$winRockScissors + +Here is the result: + +```python +>>> win(Paper(), Rock()) +1 +>>> win(Scissors(), Paper()) +1 +>>> win(Rock(), Scissors()) +1 +>>> win(Paper(), Paper()) +0 +>>> win(Rock(), Rock()) +0 +>>> win(Scissors(), Scissors()) +0 +>>> win(Rock(), Paper()) +-1 +>>> win(Paper(), Scissors()) +-1 +>>> win(Scissors(), Rock()) +-1 + +``` + +The point of generic functions is that they play well with subclassing. +For instance, suppose we define a ``StrongRock``, which does not lose against +Paper: + +$$StrongRock +$$winStrongRockPaper + +Then you do not need to define other implementations; they are +inherited from the parent: + +```python +>>> win(StrongRock(), Scissors()) +1 + +``` + +You can introspect the precedence used by the dispath algorithm by +calling ``.dispatch_info(*types)``: + +```python +>>> win.dispatch_info(StrongRock, Scissors) +[('StrongRock', 'Scissors'), ('Rock', 'Scissors')] + +``` + +Since there is no direct implementation for (``StrongRock``, ``Scissors``), +the dispatcher will look at the implementation for (``Rock``, ``Scissors``) +which is available. Internally, the algorithm is doing a cross +product of the class precedence lists (or *Method Resolution Orders*, +[MRO](http://www.python.org/2.3/mro.html) for short) of ``StrongRock`` + and ``Scissors``, respectively. + +Generic functions and virtual ancestors +------------------------------------------------- + +In Python, generic functions are complicated by the existence of +"virtual ancestors": superclasses which are not in the class hierarchy. + +Consider this class: + +$$WithLength + +This class defines a ``__len__`` method, and is therefore +considered to be a subclass of the abstract base class +``collections.abc.Sized`` (``collections.Sized`` on Python 2): + +```python +>>> issubclass(WithLength, collections.abc.Sized) +True + +``` + +However, ``collections.abc.Sized`` is not in the MRO_ of ``WithLength``; it +is not a true ancestor. Any implementation of generic functions (even +with single dispatch) must go through some contorsion to take into +account the virtual ancestors. + +In particular, if we define a generic function... + +$$get_length + +...implemented on all classes with a length... + +$$get_length_sized + +...then ``get_length`` must be defined on ``WithLength`` instances... + +```python +>>> get_length(WithLength()) +0 + +``` + +...even if ``collections.abc.Sized`` is not a true ancestor of ``WithLength``. + +Of course, this is a contrived example--you could just use the +builtin ``len``--but you should get the idea. + +Since in Python it is possible to consider any instance of ``ABCMeta`` +as a virtual ancestor of any other class (it is enough to register it +as ``ancestor.register(cls)``), any implementation of generic functions +must be aware of the registration mechanism. + +For example, suppose you are using a third-party set-like class, like +the following: + +$$SomeSet + +Here, the author of ``SomeSet`` made a mistake by inheriting from +``collections.abc.Sized`` (instead of ``collections.abc.Set``). + +This is not a problem. You can register *a posteriori* +``collections.abc.Set`` as a virtual ancestor of ``SomeSet``: + +```python +>>> _ = collections.abc.Set.register(SomeSet) +>>> issubclass(SomeSet, collections.abc.Set) +True + +``` + +Now, let's define an implementation of ``get_length`` specific to set: + +$$get_length_set + +The current implementation (and ``functools.singledispatch`` too) +is able to discern that a ``Set`` is a ``Sized`` object, by looking at +the class registry, so it uses the more specific implementation for ``Set``: + +```python +>>> get_length(SomeSet()) # NB: the implementation for Sized would give 0 +1 + +``` + +Sometimes it is not clear how to dispatch. For instance, consider a +class ``C`` registered both as ``collections.abc.Iterable`` and +``collections.abc.Sized``, and defines a generic function ``g`` with +implementations for both ``collections.abc.Iterable`` *and* +``collections.abc.Sized``: + +$$singledispatch_example1 + +It is impossible to decide which implementation to use, since the ancestors +are independent. The following function will raise a ``RuntimeError`` +when called. This is consistent with the "refuse the temptation to guess" +philosophy. ``functools.singledispatch`` would raise a similar error. + +It would be easy to rely on the order of registration to decide the +precedence order. This is reasonable, but also fragile: + +- if, during some refactoring, you change the registration order by mistake, + a different implementation could be taken; +- if implementations of the generic functions are distributed across modules, + and you change the import order, a different implementation could be taken. + +So the ``decorator`` module prefers to raise an error in the face of ambiguity. +This is the same approach taken by the standard library. + +However, it should be noted that the *dispatch algorithm* used by the decorator +module is different from the one used by the standard library, so in certain +cases you will get different answers. The difference is that +``functools.singledispatch`` tries to insert the virtual ancestors *before* the +base classes, whereas ``decorator.dispatch_on`` tries to insert them *after* +the base classes. + +Here's an example that shows the difference: + +$$singledispatch_example2 + +If you play with this example and replace the ``singledispatch`` definition +with ``functools.singledispatch``, the assertion will break: ``g`` will return +``"container"`` instead of ``"s"``, because ``functools.singledispatch`` +will insert the ``Container`` class right before ``S``. + +Notice that here I am not making any bold claim such as "the standard +library algorithm is wrong and my algorithm is right" or viceversa. It +just point out that there are some subtle differences. The only way to +understand what is really happening here is to scratch your head by +looking at the implementations. I will just notice that +``.dispatch_info`` is quite essential to see the class precedence +list used by algorithm: + +```python +>>> g, V = singledispatch_example2() +>>> g.dispatch_info(V) +[('V',), ('Sized',), ('S',), ('Container',)] + +``` + +The current implementation does not implement any kind of cooperation +between implementations. In other words, nothing is akin either to +call-next-method in Lisp, or to ``super`` in Python. + +Finally, let me notice that the decorator module implementation does +not use any cache, whereas the ``singledispatch`` implementation does. + +Caveats and limitations +------------------------------------------- + +One thing you should be aware of, is the performance penalty of decorators. +The worse case is shown by the following example: + +```bash + $ cat performance.sh + python3 -m timeit -s " + from decorator import decorator + + @decorator + def do_nothing(func, *args, **kw): + return func(*args, **kw) + + @do_nothing + def f(): + pass + " "f()" + + python3 -m timeit -s " + def f(): + pass + " "f()" + +``` +On my laptop, using the ``do_nothing`` decorator instead of the +plain function is five times slower: + +```bash + $ bash performance.sh + 1000000 loops, best of 3: 1.39 usec per loop + 1000000 loops, best of 3: 0.278 usec per loop +``` +Of course, a real life function probably does something more useful +than the function ``f`` here, so the real life performance penalty +*could* be negligible. As always, the only way to know if there is a +penalty in your specific use case is to measure it. + +More importantly, you should be aware that decorators will make your +tracebacks longer and more difficult to understand. + +Consider this example: + +```python +>>> @trace +... def f(): +... 1/0 + +``` + +Calling ``f()`` gives you a ``ZeroDivisionError``. +But since the function is decorated, the traceback is longer: + +```python +>>> f() # doctest: +ELLIPSIS +Traceback (most recent call last): + ... + File "<string>", line 2, in f + File "<doctest __main__[22]>", line 4, in trace + return f(*args, **kw) + File "<doctest __main__[51]>", line 3, in f + 1/0 +ZeroDivisionError: ... + +``` + +You see here the inner call to the decorator ``trace``, which calls +``f(*args, **kw)``, and a reference to ``File "<string>", line 2, in f``. + +This latter reference is due to the fact that, internally, the decorator +module uses ``exec`` to generate the decorated function. Notice that +``exec`` is *not* responsible for the performance penalty, since is the +called *only once* (at function decoration time); it is *not* called +each time the decorated function is called. + +Presently, there is no clean way to avoid ``exec``. A clean solution +would require changing the CPython implementation, by +adding a hook to functions (to allow changing their signature directly). + +Even in Python 3.5, it is impossible to change the +function signature directly. Thus, the ``decorator`` module is +still useful! As a matter of fact, this is the main reason why I still +maintain the module and release new versions. + +It should be noted that in Python 3.5, a *lot* of improvements have +been made: you can decorate a function with +``func_tools.update_wrapper``, and ``pydoc`` will see the correct +signature. Unfortunately, the function will still have an incorrect +signature internally, as you can see by using +``inspect.getfullargspec``; so, all documentation tools using +``inspect.getfullargspec`` - which has been rightly deprecated - +will see the wrong signature. + +In the present implementation, decorators generated by ``decorator`` +can only be used on user-defined Python functions or methods. +They cannot be used on generic callable objects or built-in functions, +due to limitations of the standard library's ``inspect`` module, especially +for Python 2. In Python 3.5, many such limitations have been removed, but +I still think that it is cleaner and safer to decorate only functions and +coroutines. If you want to decorate things like classmethods/staticmethods +and general callables - which I will never support in the decorator module - +I suggest you to look at the [wrapt](https://wrapt.readthedocs.io/en/latest/) +project by Graeme Dumpleton. + +There is a strange quirk when decorating functions with keyword +arguments, if one of the arguments has the same name used in the +caller function for the first argument. The quirk was reported by +David Goldstein. + +Here is an example where it is manifest: + +```python +>>> @memoize +... def getkeys(**kw): +... return kw.keys() + +>>> getkeys(func='a') # doctest: +ELLIPSIS +Traceback (most recent call last): + ... +TypeError: _memoize() got multiple values for ... 'func' + +``` + +The error message looks really strange... until you realize that +the caller function `_memoize` uses `func` as first argument, +so there is a confusion between the positional argument and the +keywork arguments. + +The solution is to change the name of the first argument in `_memoize`, +or to change the implementation like so: + +```python + +def _memoize(*all_args, **kw): + func = all_args[0] + args = all_args[1:] + if kw: # frozenset is used to ensure hashability + key = args, frozenset(kw.items()) + else: + key = args + cache = func.cache # attribute added by memoize + if key not in cache: + cache[key] = func(*args, **kw) + return cache[key] +``` + +This avoids the need to name the first argument, so the problem +simply disappears. This is a technique that you should keep in mind +when writing decorators for functions with keyword arguments. Also, +notice that lately I have come to believe that decorating functions with +keyword arguments is not such a good idea, and you may want not to do +that. + +On a similar note, there is a restriction on argument names. For instance, +if you name an argument ``_call_`` or ``_func_``, you will get a ``NameError``: + +```python +>>> @trace +... def f(_func_): print(f) +... +Traceback (most recent call last): + ... +NameError: _func_ is overridden in +def f(_func_): + return _call_(_func_, _func_) + +``` + +Finally, the implementation is such that the decorated function makes +a (shallow) copy of the original function dictionary: + +```python +>>> def f(): pass # the original function +>>> f.attr1 = "something" # setting an attribute +>>> f.attr2 = "something else" # setting another attribute + +>>> traced_f = trace(f) # the decorated function + +>>> traced_f.attr1 +'something' +>>> traced_f.attr2 = "something different" # setting attr +>>> f.attr2 # the original attribute did not change +'something else' + +``` + +LICENSE (2-clause BSD) +--------------------------------------------- + +Copyright (c) 2005-2020, 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. + +If you use this software and you are happy with it, consider sending me a +note, just to gratify my ego. On the other hand, if you use this software and +you are unhappy with it, send me a patch! +""" + +function_annotations = """Function annotations +--------------------------------------------- + +Python 3 introduced the concept of [function annotations]( +http://www.python.org/dev/peps/pep-3107/): the ability +to annotate the signature of a function with additional information, +stored in a dictionary named ``__annotations__``. The ``decorator`` module +(starting from release 3.3) will understand and preserve these annotations. + +Here is an example: + +```python +>>> @trace +... def f(x: 'the first argument', y: 'default argument'=1, z=2, +... *args: 'varargs', **kw: 'kwargs'): +... pass + +``` + +In order to introspect functions with annotations, one needs the +utility ``inspect.getfullargspec`` (introduced in Python 3, then +deprecated in Python 3.5, then undeprecated in Python 3.6): + +```python +>>> from inspect import getfullargspec +>>> argspec = getfullargspec(f) +>>> argspec.args +['x', 'y', 'z'] +>>> argspec.varargs +'args' +>>> argspec.varkw +'kw' +>>> argspec.defaults +(1, 2) +>>> argspec.kwonlyargs +[] +>>> argspec.kwonlydefaults + +``` + +You can check that the ``__annotations__`` dictionary is preserved: + +```python +>>> f.__annotations__ is f.__wrapped__.__annotations__ +True + +``` + +Here ``f.__wrapped__`` is the original undecorated function. +This attribute exists for consistency with the behavior of +``functools.update_wrapper``. + +Another attribute copied from the original function is ``__qualname__``, +the qualified name. This attribute was introduced in Python 3.3. +""" + +if sys.version_info < (3,): + function_annotations = '' + +today = time.strftime('%Y-%m-%d') + +__doc__ = (doc.replace('$VERSION', __version__).replace('$DATE', today) + .replace('$FUNCTION_ANNOTATIONS', function_annotations)) + + +def decorator_apply(dec, func): + """ + Decorate a function by preserving the signature even if dec + is not a signature-preserving decorator. + """ + return FunctionMaker.create( + func, 'return decfunc(%(signature)s)', + dict(decfunc=dec(func)), __wrapped__=func) + + +def _trace(f, *args, **kw): + kwstr = ', '.join('%r: %r' % (k, kw[k]) for k in sorted(kw)) + print("calling %s with args %s, {%s}" % (f.__name__, args, kwstr)) + return f(*args, **kw) + + +def trace(f): + return decorate(f, _trace) + + +class Future(threading.Thread): + """ + A class converting blocking functions into asynchronous + functions by using threads. + """ + def __init__(self, func, *args, **kw): + try: + counter = func.counter + except AttributeError: # instantiate the counter at the first call + counter = func.counter = itertools.count(1) + name = '%s-%s' % (func.__name__, next(counter)) + + def func_wrapper(): + self._result = func(*args, **kw) + super(Future, self).__init__(target=func_wrapper, name=name) + self.start() + + def result(self): + self.join() + return self._result + + +def identity_dec(func): + def wrapper(*args, **kw): + return func(*args, **kw) + return wrapper + + +@identity_dec +def example(): + pass + + +def memoize_uw(func): + func.cache = {} + + def memoize(*args, **kw): + if kw: # frozenset is used to ensure hashability + key = args, frozenset(kw.items()) + else: + key = args + if key not in func.cache: + func.cache[key] = func(*args, **kw) + return func.cache[key] + return functools.update_wrapper(memoize, func) + + +@memoize_uw +def f1(x): + "Simulate some long computation" + time.sleep(1) + return x + + +def _memoize(func, *args, **kw): + if kw: # frozenset is used to ensure hashability + key = args, frozenset(kw.items()) + else: + key = args + cache = func.cache # attribute added by memoize + if key not in cache: + cache[key] = func(*args, **kw) + return cache[key] + + +def memoize(f): + """ + A simple memoize implementation. It works by adding a .cache dictionary + to the decorated function. The cache will grow indefinitely, so it is + your responsibility to clear it, if needed. + """ + f.cache = {} + return decorate(f, _memoize) + + +@decorator +def blocking(f, msg='blocking', *args, **kw): + if not hasattr(f, "thread"): # no thread running + def set_result(): + f.result = f(*args, **kw) + f.thread = threading.Thread(None, set_result) + f.thread.start() + return msg + elif f.thread.is_alive(): + return msg + else: # the thread is ended, return the stored result + del f.thread + return f.result + + +class User(object): + "Will just be able to see a page" + + +class PowerUser(User): + "Will be able to add new pages too" + + +class Admin(PowerUser): + "Will be able to delete pages too" + + +class PermissionError(Exception): + """ + >>> a = Action() + >>> a.user = User() + >>> a.view() # ok + >>> a.insert() # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + PermissionError: User does not have the permission to run insert! + """ + + +@decorator +def restricted(func, user_class=User, *args, **kw): + "Restrict access to a given class of users" + self = args[0] + if isinstance(self.user, user_class): + return func(*args, **kw) + else: + raise PermissionError( + '%s does not have the permission to run %s!' + % (self.user, func.__name__)) + + +class Action(object): + @restricted(user_class=User) + def view(self): + "Any user can view objects" + + @restricted(user_class=PowerUser) + def insert(self): + "Only power users can insert objects" + + @restricted(user_class=Admin) + def delete(self): + "Only the admin can delete objects" + + +class TailRecursive(object): + """ + tail_recursive decorator based on Kay Schluehr's recipe + http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496691 + with improvements by me and George Sakkis. + """ + + def __init__(self, func): + self.func = func + self.firstcall = True + self.CONTINUE = object() # sentinel + + def __call__(self, *args, **kwd): + CONTINUE = self.CONTINUE + if self.firstcall: + func = self.func + self.firstcall = False + try: + while True: + result = func(*args, **kwd) + if result is CONTINUE: # update arguments + args, kwd = self.argskwd + else: # last call + return result + finally: + self.firstcall = True + else: # return the arguments of the tail call + self.argskwd = args, kwd + return CONTINUE + + +def tail_recursive(func): + return decorator_apply(TailRecursive, func) + + +@tail_recursive +def factorial(n, acc=1): + "The good old factorial" + if n == 0: + return acc + return factorial(n-1, n*acc) + + +def fact(n): # this is not tail-recursive + if n == 0: + return 1 + return n * fact(n-1) + + +def a_test_for_pylons(): + """ + In version 3.1.0 decorator(caller) returned a nameless partial + object, thus breaking Pylons. That must not happen again. + + >>> decorator(_memoize).__name__ + '_memoize' + + Here is another bug of version 3.1.1 missing the docstring: + + >>> factorial.__doc__ + 'The good old factorial' + """ + + +if sys.version_info >= (3,): # tests for signatures specific to Python 3 + + def test_kwonlydefaults(): + """ + >>> @trace + ... def f(arg, defarg=1, *args, kwonly=2): pass + ... + >>> f.__kwdefaults__ + {'kwonly': 2} + """ + + def test_kwonlyargs(): + """ + >>> @trace + ... def func(a, b, *args, y=2, z=3, **kwargs): + ... return y, z + ... + >>> func('a', 'b', 'c', 'd', 'e', y='y', z='z', cat='dog') + calling func with args ('a', 'b', 'c', 'd', 'e'), {'cat': 'dog', 'y': 'y', 'z': 'z'} + ('y', 'z') + """ + + def test_kwonly_no_args(): + """# this was broken with decorator 3.3.3 + >>> @trace + ... def f(**kw): pass + ... + >>> f() + calling f with args (), {} + """ + + def test_kwonly_star_notation(): + """ + >>> @trace + ... def f(*, a=1, **kw): pass + ... + >>> import inspect + >>> inspect.getfullargspec(f) + FullArgSpec(args=[], varargs=None, varkw='kw', defaults=None, kwonlyargs=['a'], kwonlydefaults={'a': 1}, annotations={}) + """ + + +@contextmanager +def before_after(before, after): + print(before) + yield + print(after) + + +ba = before_after('BEFORE', 'AFTER') # ContextManager instance + + +@ba +def hello(user): + """ + >>> ba.__class__.__name__ + 'ContextManager' + >>> hello('michele') + BEFORE + hello michele + AFTER + """ + print('hello %s' % user) + + +# ####################### multiple dispatch ############################ # + + +class XMLWriter(object): + def __init__(self, **config): + self.cfg = config + + @dispatch_on('obj') + def write(self, obj): + raise NotImplementedError(type(obj)) + + +@XMLWriter.write.register(float) +def writefloat(self, obj): + return '<float>%s</float>' % obj + + +class Rock(object): + ordinal = 0 + + +class Paper(object): + ordinal = 1 + + +class Scissors(object): + ordinal = 2 + + +class StrongRock(Rock): + pass + + +@dispatch_on('a', 'b') +def win(a, b): + if a.ordinal == b.ordinal: + return 0 + elif a.ordinal > b.ordinal: + return -win(b, a) + raise NotImplementedError((type(a), type(b))) + + +@win.register(Rock, Paper) +def winRockPaper(a, b): + return -1 + + +@win.register(Rock, Scissors) +def winRockScissors(a, b): + return 1 + + +@win.register(Paper, Scissors) +def winPaperScissors(a, b): + return -1 + + +@win.register(StrongRock, Paper) +def winStrongRockPaper(a, b): + return 0 + + +class WithLength(object): + def __len__(self): + return 0 + + +class SomeSet(collections.abc.Sized): + # methods that make SomeSet set-like + # not shown ... + def __len__(self): + return 0 + + +@dispatch_on('obj') +def get_length(obj): + raise NotImplementedError(type(obj)) + + +@get_length.register(collections.abc.Sized) +def get_length_sized(obj): + return len(obj) + + +@get_length.register(collections.abc.Set) +def get_length_set(obj): + return 1 + + +class C(object): + "Registered as Sized and Iterable" + + +collections.abc.Sized.register(C) +collections.abc.Iterable.register(C) + + +def singledispatch_example1(): + singledispatch = dispatch_on('obj') + + @singledispatch + def g(obj): + raise NotImplementedError(type(g)) + + @g.register(collections.abc.Sized) + def g_sized(object): + return "sized" + + @g.register(collections.abc.Iterable) + def g_iterable(object): + return "iterable" + + g(C()) # RuntimeError: Ambiguous dispatch: Iterable or Sized? + + +def singledispatch_example2(): + # adapted from functools.singledispatch test case + singledispatch = dispatch_on('arg') + + class S(object): + pass + + class V(c.Sized, S): + def __len__(self): + return 0 + + @singledispatch + def g(arg): + return "base" + + @g.register(S) + def g_s(arg): + return "s" + + @g.register(c.Container) + def g_container(arg): + return "container" + + v = V() + assert g(v) == "s" + c.Container.register(V) # add c.Container to the virtual mro of V + assert g(v) == "s" # since the virtual mro is V, Sized, S, Container + return g, V + + +@decorator +def warn_slow(func, duration=0, *args, **kwargs): + t0 = time.time() + res = func(*args, **kwargs) + dt = time.time() - t0 + if dt >= duration: + print('%s is slow' % func.__name__) + return res + + +@warn_slow() # with parens +def operation1(): + """ + >>> operation1() + operation1 is slow + """ + time.sleep(.1) + + +@warn_slow # without parens +def operation2(): + """ + >>> operation2() + operation2 is slow + """ + time.sleep(.1) + + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/contrib/python/decorator/py3/tests/test.py b/contrib/python/decorator/py3/tests/test.py new file mode 100644 index 0000000000..7ddfaf45b9 --- /dev/null +++ b/contrib/python/decorator/py3/tests/test.py @@ -0,0 +1,480 @@ +from __future__ import absolute_import +import sys +import doctest +import unittest +import decimal +import inspect +import functools +import collections +from collections import defaultdict +try: + c = collections.abc +except AttributeError: + c = collections +from decorator import dispatch_on, contextmanager, decorator +try: + from . import documentation as doc +except (ImportError, ValueError, SystemError): # depending on the py-version + import documentation as doc + + +@contextmanager +def assertRaises(etype): + """This works in Python 2.6 too""" + try: + yield + except etype: + pass + else: + raise Exception('Expected %s' % etype.__name__) + + +if sys.version_info >= (3, 5): + exec('''from asyncio import get_event_loop + +@decorator +async def before_after(coro, *args, **kwargs): + return "<before>" + (await coro(*args, **kwargs)) + "<after>" + +@decorator +def coro_to_func(coro, *args, **kw): + return get_event_loop().run_until_complete(coro(*args, **kw)) + +class CoroutineTestCase(unittest.TestCase): + def test_before_after(self): + @before_after + async def coro(x): + return x + self.assertTrue(inspect.iscoroutinefunction(coro)) + out = get_event_loop().run_until_complete(coro('x')) + self.assertEqual(out, '<before>x<after>') + + def test_coro_to_func(self): + @coro_to_func + async def coro(x): + return x + self.assertFalse(inspect.iscoroutinefunction(coro)) + self.assertEqual(coro('x'), 'x') +''') + + +def gen123(): + yield 1 + yield 2 + yield 3 + + +class GeneratorCallerTestCase(unittest.TestCase): + def test_gen123(self): + @decorator + def square(func, *args, **kw): + for x in gen123(): + yield x * x + new = square(gen123) + self.assertTrue(inspect.isgeneratorfunction(new)) + self.assertEqual(list(new()), [1, 4, 9]) + + +class DocumentationTestCase(unittest.TestCase): + def test(self): + err = doctest.testmod(doc)[0] + self.assertEqual(err, 0) + + def test_singledispatch1(self): + if hasattr(functools, 'singledispatch'): + with assertRaises(RuntimeError): + doc.singledispatch_example1() + + def test_singledispatch2(self): + if hasattr(functools, 'singledispatch'): + doc.singledispatch_example2() + + +class ExtraTestCase(unittest.TestCase): + def test_qualname(self): + if sys.version_info >= (3, 3): + self.assertEqual(doc.hello.__qualname__, 'hello') + else: + with assertRaises(AttributeError): + doc.hello.__qualname__ + + def test_signature(self): + if hasattr(inspect, 'signature'): + sig = inspect.signature(doc.f1) + self.assertEqual(str(sig), '(x)') + + def test_unique_filenames(self): + @decorator + def d1(f, *args, **kwargs): + return f(*args, **kwargs) + + @decorator + def d2(f, *args, **kwargs): + return f(*args, **kwargs) + + @d1 + def f1(x, y, z): + pass + + @d2 + def f2(x, y, z): + pass + + f1_orig = f1 + + @d1 + def f1(x, y, z): + pass + self.assertNotEqual(d1.__code__.co_filename, d2.__code__.co_filename) + self.assertNotEqual(f1.__code__.co_filename, f2.__code__.co_filename) + self.assertNotEqual(f1_orig.__code__.co_filename, + f1.__code__.co_filename) + + def test_no_first_arg(self): + @decorator + def example(*args, **kw): + return args[0](*args[1:], **kw) + + @example + def func(**kw): + return kw + + # there is no confusion when passing args as a keyword argument + self.assertEqual(func(args='a'), {'args': 'a'}) + + def test_decorator_factory(self): + # similar to what IPython is doing in traitlets.config.application + @decorator + def catch_config_error(method, app, *args, **kwargs): + return method(app) + catch_config_error(lambda app: None) + + def test_add1(self): + # similar to what IPython is doing in traitlets.config.application + @decorator + def add(func, const=1, *args, **kwargs): + return const + func(*args, **kwargs) + + def f(x): + return x + self.assertEqual(add(f, 2)(0), 2) + + +# ################### test dispatch_on ############################# # +# adapted from test_functools in Python 3.5 +singledispatch = dispatch_on('obj') + + +class TestSingleDispatch(unittest.TestCase): + def test_simple_overloads(self): + @singledispatch + def g(obj): + return "base" + + @g.register(int) + def g_int(i): + return "integer" + + self.assertEqual(g("str"), "base") + self.assertEqual(g(1), "integer") + self.assertEqual(g([1, 2, 3]), "base") + + def test_mro(self): + @singledispatch + def g(obj): + return "base" + + class A(object): + pass + + class C(A): + pass + + class B(A): + pass + + class D(C, B): + pass + + @g.register(A) + def g_A(a): + return "A" + + @g.register(B) + def g_B(b): + return "B" + + self.assertEqual(g(A()), "A") + self.assertEqual(g(B()), "B") + self.assertEqual(g(C()), "A") + self.assertEqual(g(D()), "B") + + def test_register_decorator(self): + @singledispatch + def g(obj): + return "base" + + @g.register(int) + def g_int(i): + return "int %s" % (i,) + self.assertEqual(g(""), "base") + self.assertEqual(g(12), "int 12") + + def test_register_error(self): + @singledispatch + def g(obj): + return "base" + + with assertRaises(TypeError): + # wrong number of arguments + @g.register(int) + def g_int(): + return "int" + + def test_wrapping_attributes(self): + @singledispatch + def g(obj): + "Simple test" + return "Test" + self.assertEqual(g.__name__, "g") + if sys.flags.optimize < 2: + self.assertEqual(g.__doc__, "Simple test") + + def test_c_classes(self): + @singledispatch + def g(obj): + return "base" + + @g.register(decimal.DecimalException) + def _(obj): + return obj.args + subn = decimal.Subnormal("Exponent < Emin") + rnd = decimal.Rounded("Number got rounded") + self.assertEqual(g(subn), ("Exponent < Emin",)) + self.assertEqual(g(rnd), ("Number got rounded",)) + + @g.register(decimal.Subnormal) + def _g(obj): + return "Too small to care." + self.assertEqual(g(subn), "Too small to care.") + self.assertEqual(g(rnd), ("Number got rounded",)) + + def test_register_abc(self): + d = {"a": "b"} + l = [1, 2, 3] + s = set([object(), None]) + f = frozenset(s) + t = (1, 2, 3) + + @singledispatch + def g(obj): + return "base" + + self.assertEqual(g(d), "base") + self.assertEqual(g(l), "base") + self.assertEqual(g(s), "base") + self.assertEqual(g(f), "base") + self.assertEqual(g(t), "base") + + g.register(c.Sized)(lambda obj: "sized") + self.assertEqual(g(d), "sized") + self.assertEqual(g(l), "sized") + self.assertEqual(g(s), "sized") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sized") + + g.register(c.MutableMapping)(lambda obj: "mutablemapping") + self.assertEqual(g(d), "mutablemapping") + self.assertEqual(g(l), "sized") + self.assertEqual(g(s), "sized") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sized") + + if hasattr(c, 'ChainMap'): + g.register(c.ChainMap)(lambda obj: "chainmap") + # irrelevant ABCs registered + self.assertEqual(g(d), "mutablemapping") + self.assertEqual(g(l), "sized") + self.assertEqual(g(s), "sized") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sized") + + g.register(c.MutableSequence)(lambda obj: "mutablesequence") + self.assertEqual(g(d), "mutablemapping") + self.assertEqual(g(l), "mutablesequence") + self.assertEqual(g(s), "sized") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sized") + + g.register(c.MutableSet)(lambda obj: "mutableset") + self.assertEqual(g(d), "mutablemapping") + self.assertEqual(g(l), "mutablesequence") + self.assertEqual(g(s), "mutableset") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sized") + + g.register(c.Mapping)(lambda obj: "mapping") + self.assertEqual(g(d), "mutablemapping") # not specific enough + self.assertEqual(g(l), "mutablesequence") + self.assertEqual(g(s), "mutableset") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sized") + + g.register(c.Sequence)(lambda obj: "sequence") + self.assertEqual(g(d), "mutablemapping") + self.assertEqual(g(l), "mutablesequence") + self.assertEqual(g(s), "mutableset") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sequence") + + g.register(c.Set)(lambda obj: "set") + self.assertEqual(g(d), "mutablemapping") + self.assertEqual(g(l), "mutablesequence") + self.assertEqual(g(s), "mutableset") + self.assertEqual(g(f), "set") + self.assertEqual(g(t), "sequence") + + g.register(dict)(lambda obj: "dict") + self.assertEqual(g(d), "dict") + self.assertEqual(g(l), "mutablesequence") + self.assertEqual(g(s), "mutableset") + self.assertEqual(g(f), "set") + self.assertEqual(g(t), "sequence") + + g.register(list)(lambda obj: "list") + self.assertEqual(g(d), "dict") + self.assertEqual(g(l), "list") + self.assertEqual(g(s), "mutableset") + self.assertEqual(g(f), "set") + self.assertEqual(g(t), "sequence") + + g.register(set)(lambda obj: "concrete-set") + self.assertEqual(g(d), "dict") + self.assertEqual(g(l), "list") + self.assertEqual(g(s), "concrete-set") + self.assertEqual(g(f), "set") + self.assertEqual(g(t), "sequence") + + g.register(frozenset)(lambda obj: "frozen-set") + self.assertEqual(g(d), "dict") + self.assertEqual(g(l), "list") + self.assertEqual(g(s), "concrete-set") + self.assertEqual(g(f), "frozen-set") + self.assertEqual(g(t), "sequence") + + g.register(tuple)(lambda obj: "tuple") + self.assertEqual(g(d), "dict") + self.assertEqual(g(l), "list") + self.assertEqual(g(s), "concrete-set") + self.assertEqual(g(f), "frozen-set") + self.assertEqual(g(t), "tuple") + + def test_mro_conflicts(self): + @singledispatch + def g(obj): + return "base" + + class O(c.Sized): + def __len__(self): + return 0 + o = O() + self.assertEqual(g(o), "base") + g.register(c.Iterable)(lambda arg: "iterable") + g.register(c.Container)(lambda arg: "container") + g.register(c.Sized)(lambda arg: "sized") + g.register(c.Set)(lambda arg: "set") + self.assertEqual(g(o), "sized") + c.Iterable.register(O) + self.assertEqual(g(o), "sized") + c.Container.register(O) + with assertRaises(RuntimeError): # was "sized" because in mro + self.assertEqual(g(o), "sized") + c.Set.register(O) + self.assertEqual(g(o), "set") + + class P(object): + pass + p = P() + self.assertEqual(g(p), "base") + c.Iterable.register(P) + self.assertEqual(g(p), "iterable") + c.Container.register(P) + + with assertRaises(RuntimeError): + self.assertEqual(g(p), "iterable") + + class Q(c.Sized): + def __len__(self): + return 0 + q = Q() + self.assertEqual(g(q), "sized") + c.Iterable.register(Q) + self.assertEqual(g(q), "sized") + c.Set.register(Q) + self.assertEqual(g(q), "set") + # because c.Set is a subclass of c.Sized and c.Iterable + + @singledispatch + def h(obj): + return "base" + + @h.register(c.Sized) + def h_sized(arg): + return "sized" + + @h.register(c.Container) + def h_container(arg): + return "container" + # Even though Sized and Container are explicit bases of MutableMapping, + # this ABC is implicitly registered on defaultdict which makes all of + # MutableMapping's bases implicit as well from defaultdict's + # perspective. + with assertRaises(RuntimeError): + self.assertEqual(h(defaultdict(lambda: 0)), "sized") + + class R(defaultdict): + pass + c.MutableSequence.register(R) + + @singledispatch + def i(obj): + return "base" + + @i.register(c.MutableMapping) + def i_mapping(arg): + return "mapping" + + @i.register(c.MutableSequence) + def i_sequence(arg): + return "sequence" + r = R() + with assertRaises(RuntimeError): # was no error + self.assertEqual(i(r), "sequence") + + class S(object): + pass + + class T(S, c.Sized): + def __len__(self): + return 0 + t = T() + self.assertEqual(h(t), "sized") + c.Container.register(T) + self.assertEqual(h(t), "sized") # because it's explicitly in the MRO + + class U(object): + def __len__(self): + return 0 + u = U() + self.assertEqual(h(u), "sized") + # implicit Sized subclass inferred + # from the existence of __len__() + + c.Container.register(U) + # There is no preference for registered versus inferred ABCs. + with assertRaises(RuntimeError): + h(u) + + +if __name__ == '__main__': + unittest.main() diff --git a/contrib/python/decorator/py3/tests/ya.make b/contrib/python/decorator/py3/tests/ya.make new file mode 100644 index 0000000000..173334b4b5 --- /dev/null +++ b/contrib/python/decorator/py3/tests/ya.make @@ -0,0 +1,19 @@ +PY3TEST() + +PEERDIR( + contrib/python/decorator +) + +PY_SRCS( + TOP_LEVEL + documentation.py +) + +TEST_SRCS( + #documentation.py + test.py +) + +NO_LINT() + +END() diff --git a/contrib/python/decorator/py3/ya.make b/contrib/python/decorator/py3/ya.make new file mode 100644 index 0000000000..e6b2b2bdeb --- /dev/null +++ b/contrib/python/decorator/py3/ya.make @@ -0,0 +1,26 @@ +# Generated by devtools/yamaker (pypi). + +PY3_LIBRARY() + +VERSION(4.4.2) + +LICENSE(BSD-3-Clause) + +NO_LINT() + +PY_SRCS( + TOP_LEVEL + decorator.py +) + +RESOURCE_FILES( + PREFIX contrib/python/decorator/py3/ + .dist-info/METADATA + .dist-info/top_level.txt +) + +END() + +RECURSE_FOR_TESTS( + tests +) diff --git a/contrib/python/decorator/ya.make b/contrib/python/decorator/ya.make new file mode 100644 index 0000000000..7431bc5c75 --- /dev/null +++ b/contrib/python/decorator/ya.make @@ -0,0 +1,18 @@ +PY23_LIBRARY() + +LICENSE(Service-Py23-Proxy) + +IF (PYTHON2) + PEERDIR(contrib/python/decorator/py2) +ELSE() + PEERDIR(contrib/python/decorator/py3) +ENDIF() + +NO_LINT() + +END() + +RECURSE( + py2 + py3 +) |