diff options
author | shadchin <shadchin@yandex-team.ru> | 2022-02-10 16:44:30 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:44:30 +0300 |
commit | 2598ef1d0aee359b4b6d5fdd1758916d5907d04f (patch) | |
tree | 012bb94d777798f1f56ac1cec429509766d05181 /contrib/python/decorator | |
parent | 6751af0b0c1b952fede40b19b71da8025b5d8bcf (diff) | |
download | ydb-2598ef1d0aee359b4b6d5fdd1758916d5907d04f.tar.gz |
Restoring authorship annotation for <shadchin@yandex-team.ru>. Commit 1 of 2.
Diffstat (limited to 'contrib/python/decorator')
-rw-r--r-- | contrib/python/decorator/.dist-info/METADATA | 262 | ||||
-rw-r--r-- | contrib/python/decorator/.dist-info/top_level.txt | 2 | ||||
-rw-r--r-- | contrib/python/decorator/LICENSE.txt | 2 | ||||
-rw-r--r-- | contrib/python/decorator/README.rst | 196 | ||||
-rw-r--r-- | contrib/python/decorator/decorator.py | 60 | ||||
-rw-r--r-- | contrib/python/decorator/tests/documentation.py | 3794 | ||||
-rw-r--r-- | contrib/python/decorator/tests/test.py | 960 | ||||
-rw-r--r-- | contrib/python/decorator/tests/ya.make | 42 | ||||
-rw-r--r-- | contrib/python/decorator/ya.make | 24 |
9 files changed, 2671 insertions, 2671 deletions
diff --git a/contrib/python/decorator/.dist-info/METADATA b/contrib/python/decorator/.dist-info/METADATA index fd12277a01..a822dd4b5a 100644 --- a/contrib/python/decorator/.dist-info/METADATA +++ b/contrib/python/decorator/.dist-info/METADATA @@ -1,131 +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! - - +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/.dist-info/top_level.txt b/contrib/python/decorator/.dist-info/top_level.txt index 3fe18a4d1c..428e6fcb4a 100644 --- a/contrib/python/decorator/.dist-info/top_level.txt +++ b/contrib/python/decorator/.dist-info/top_level.txt @@ -1 +1 @@ -decorator +decorator diff --git a/contrib/python/decorator/LICENSE.txt b/contrib/python/decorator/LICENSE.txt index b0ade0487e..39987543a8 100644 --- a/contrib/python/decorator/LICENSE.txt +++ b/contrib/python/decorator/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2005-2018, Michele Simionato +Copyright (c) 2005-2018, Michele Simionato All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/contrib/python/decorator/README.rst b/contrib/python/decorator/README.rst index f56161c74a..23f6f685a0 100644 --- a/contrib/python/decorator/README.rst +++ b/contrib/python/decorator/README.rst @@ -1,98 +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! +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/decorator.py b/contrib/python/decorator/decorator.py index b1f8b567e9..9193be59b6 100644 --- a/contrib/python/decorator/decorator.py +++ b/contrib/python/decorator/decorator.py @@ -40,9 +40,9 @@ import operator import itertools import collections -__version__ = '4.4.2' +__version__ = '4.4.2' -if sys.version_info >= (3,): +if sys.version_info >= (3,): from inspect import getfullargspec def get_init(cls): @@ -65,12 +65,12 @@ 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 +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*\(') @@ -179,7 +179,7 @@ class FunctionMaker(object): # 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) + filename = '<decorator-gen-%d>' % next(self._compile_count) try: code = compile(src, filename, 'single') exec(code, evaldict) @@ -224,8 +224,8 @@ class FunctionMaker(object): 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. + If the caller is a generator function, the resulting function + will be a generator function. """ evaldict = dict(_call_=caller, _func_=func) es = '' @@ -233,23 +233,23 @@ def decorate(func, caller, 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 '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 @@ -283,12 +283,12 @@ def decorator(caller, _func=None): doc = caller.__call__.__doc__ evaldict = dict(_call=caller, _decorate_=decorate) dec = FunctionMaker.create( - '%s(func, %s)' % (name, defaultargs), + '%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 + dec.__defaults__ = (None,) + defaults return dec diff --git a/contrib/python/decorator/tests/documentation.py b/contrib/python/decorator/tests/documentation.py index 46a932aa77..c17fd5c4a2 100644 --- a/contrib/python/decorator/tests/documentation.py +++ b/contrib/python/decorator/tests/documentation.py @@ -1,1897 +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() +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/tests/test.py b/contrib/python/decorator/tests/test.py index 7ddfaf45b9..9ff4c5848c 100644 --- a/contrib/python/decorator/tests/test.py +++ b/contrib/python/decorator/tests/test.py @@ -1,480 +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() +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/tests/ya.make b/contrib/python/decorator/tests/ya.make index e2734d2104..c0cbe54637 100644 --- a/contrib/python/decorator/tests/ya.make +++ b/contrib/python/decorator/tests/ya.make @@ -1,21 +1,21 @@ -PY23_TEST() - -OWNER(g:python-contrib) - -PEERDIR( - contrib/python/decorator -) - -PY_SRCS( - TOP_LEVEL - documentation.py -) - -TEST_SRCS( - #documentation.py - test.py -) - -NO_LINT() - -END() +PY23_TEST() + +OWNER(g:python-contrib) + +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/ya.make b/contrib/python/decorator/ya.make index fbb7ebf626..9228a6a277 100644 --- a/contrib/python/decorator/ya.make +++ b/contrib/python/decorator/ya.make @@ -2,9 +2,9 @@ PY23_LIBRARY() LICENSE(BSD-3-Clause) -OWNER(g:python-contrib borman) +OWNER(g:python-contrib borman) -VERSION(4.4.2) +VERSION(4.4.2) NO_LINT() @@ -13,14 +13,14 @@ PY_SRCS( decorator.py ) -RESOURCE_FILES( - PREFIX contrib/python/decorator/ - .dist-info/METADATA - .dist-info/top_level.txt -) - +RESOURCE_FILES( + PREFIX contrib/python/decorator/ + .dist-info/METADATA + .dist-info/top_level.txt +) + END() - -RECURSE_FOR_TESTS( - tests -) + +RECURSE_FOR_TESTS( + tests +) |