diff options
Diffstat (limited to 'documentation3.py')
| -rw-r--r-- | documentation3.py | 1208 |
1 files changed, 0 insertions, 1208 deletions
diff --git a/documentation3.py b/documentation3.py deleted file mode 100644 index dea3821..0000000 --- a/documentation3.py +++ /dev/null @@ -1,1208 +0,0 @@ -r""" -The ``decorator`` module -============================================================= - -:Author: Michele Simionato -:E-mail: michele.simionato@gmail.com -:Version: $VERSION ($DATE) -:Requires: Python 2.4+ -:Download page: http://pypi.python.org/pypi/decorator/$VERSION -:Installation: ``easy_install decorator`` -:License: BSD license - -.. contents:: - -Introduction ------------------------------------------------- - -Python decorators are an interesting example of why syntactic sugar -matters. In principle, their introduction in Python 2.4 changed -nothing, since they do 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 -this documentation 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: - -+ *signature-preserving* decorators, i.e. callable objects taking a - function as input and returning a function *with the same - signature* as output; - -+ *signature-changing* decorators, i.e. decorators that change - the signature of their input function, or decorators returning - non-callable objects. - -Signature-changing decorators have their use: for instance the -builtin classes ``staticmethod`` and ``classmethod`` are in this -group, since they take functions and return descriptor objects which -are not functions, nor callables. - -However, signature-preserving decorators are more common and easier to -reason about; in particular signature-preserving decorators can be -composed together whereas other decorators in general 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. -A simple implementation could be the following (notice -that in general it is impossible to memoize correctly something -that depends on non-hashable arguments): - -$$memoize_uw - -Here we used the functools.update_wrapper_ utility, which has -been added in Python 2.5 expressly to simplify the definition of decorators -(in older versions of Python you need to copy the function attributes -``__name__``, ``__doc__``, ``__module__`` and ``__dict__`` -from the original function to the decorated function by hand). - -.. _functools.update_wrapper: https://docs.python.org/3/library/functools.html#functools.update_wrapper - -The implementation above works in the sense that the decorator -can accept functions with generic signatures; unfortunately this -implementation does *not* define a signature-preserving decorator, since in -general ``memoize_uw`` returns a function with a -*different signature* from the original function. - -Consider for instance the following case: - -.. code-block:: python - - >>> @memoize_uw - ... def f1(x): - ... time.sleep(1) # simulate some long computation - ... return x - -Here the original function takes a single argument named ``x``, -but the decorated function takes any number of arguments and -keyword arguments: - -.. code-block:: python - - >>> from inspect import getargspec - >>> print(getargspec(f1)) - ArgSpec(args=[], varargs='args', keywords='kw', defaults=None) - -This means that introspection tools such as pydoc will give -wrong informations about the signature of ``f1``. This is pretty bad: -pydoc will tell you that the function accepts a generic signature -``*args``, ``**kw``, but when you try to call the function with more than an -argument, you will get an error: - -.. code-block:: python - - >>> f1(0, 1) # doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - TypeError: f1() takes exactly 1 positional argument (2 given) - -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 ``decorator`` function in -the ``decorator`` module is such a factory: - -.. code-block:: python - - >>> from decorator import decorator - -``decorator`` takes two arguments, a caller function describing the -functionality of the decorator and a function to be decorated; it -returns the decorated function. 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, -i.e. memoization in this case: - -$$_memoize - -At this point you can define your decorator as follows: - -$$memoize - -The difference with respect to the ``memoize_uw`` approach, which is based -on nested functions, is that the decorator module forces you to lift -the inner function at the outer level (*flat is better than nested*). -Moreover, you are forced to pass explicitly the function you want to -decorate to the caller function. - -Here is a test of usage: - -.. code-block:: 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: - -.. code-block:: python - - >>> print(getargspec(heavy_computation)) - ArgSpec(args=[], varargs=None, keywords=None, defaults=None) - -A ``trace`` decorator ------------------------------------------------------- - -As an additional example, here is how you can define a trivial -``trace`` decorator, which prints a message everytime the traced -function is called: - -$$_trace - -$$trace - -Here is an example of usage: - -.. code-block:: python - - >>> @trace - ... def f1(x): - ... pass - -It is immediate to verify that ``f1`` works - -.. code-block:: python - - >>> f1(0) - calling f1 with args (0,), {} - -and it that it has the correct signature: - -.. code-block:: python - - >>> print(getargspec(f1)) - ArgSpec(args=['x'], varargs=None, keywords=None, defaults=None) - -The same decorator works with functions of any signature: - -.. code-block:: python - - >>> @trace - ... def f(x, y=1, z=2, *args, **kw): - ... pass - - >>> f(0, 3) - calling f with args (0, 3, 2), {} - - >>> print(getargspec(f)) - ArgSpec(args=['x', 'y', 'z'], varargs='args', keywords='kw', defaults=(1, 2)) - -Function annotations ---------------------------------------------- - -Python 3 introduced the concept of `function annotations`_,i.e. 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, is able to understand and to preserve the -annotations. Here is an example: - -.. code-block:: 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``, new in Python 3: - -.. code-block:: 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 also check that the ``__annotations__`` dictionary is preserved: - -.. code-block:: python - - >>> f.__annotations__ == f.__wrapped__.__annotations__ - True - -Depending on the version of the decorator module, the two dictionaries can -be the same object or not: you cannot rely on object identity, but you can -rely on the content being the same. - -``decorator`` is a decorator ---------------------------------------------- - -It may be annoying to write a caller function (like the ``_trace`` -function above) and then a trivial wrapper -(``def trace(f): return decorator(_trace, f)``) every time. For this reason, -the ``decorator`` module provides an easy shortcut to convert -the caller function into a signature-preserving decorator: -you can just call ``decorator`` with a single argument. -In our example you can just write ``trace = decorator(_trace)``. -The ``decorator`` function can also be used as a signature-changing -decorator, just as ``classmethod`` and ``staticmethod``. -However, ``classmethod`` and ``staticmethod`` return generic -objects which are not callable, while ``decorator`` returns -signature-preserving decorators, i.e. functions of a single argument. -For instance, you can write directly - -.. code-block:: 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 now ``trace`` will be a decorator. Actually ``trace`` is a ``partial`` -object which can be used as a decorator: - -.. code-block:: python - - >>> trace # doctest: +ELLIPSIS - <function trace at 0x...> - -Here is an example of usage: - -.. code-block:: python - - >>> @trace - ... def func(): pass - - >>> func() - calling func with args (), {} - -If you are using an old Python version (Python 2.4) the -``decorator`` module provides a poor man replacement for -``functools.partial``. - -``blocking`` -------------------------------------------- - -Sometimes one has to deal with blocking resources, such as ``stdin``, and -sometimes it is best to have back a "busy" message than to block everything. -This behavior can be implemented with a suitable family of decorators, -where the parameter is 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: - -.. code-block:: python - - >>> @blocking("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 - -``async`` --------------------------------------------- - -We have just seen an examples of a simple decorator factory, -implemented as a function returning a decorator. -For more complex situations, it is more -convenient to implement decorator factories as classes returning -callable objects that can be converted into decorators. - -As an example, here will I show a decorator -which is able to convert a blocking function into an asynchronous -function. The function, when called, -is executed in a separate thread. Moreover, it is possible to set -three callbacks ``on_success``, ``on_failure`` and ``on_closing``, -to specify how to manage the function call (of course the code here -is just an example, it is not a recommended way of doing multi-threaded -programming). The implementation is the following: - -$$on_success -$$on_failure -$$on_closing -$$Async - -The decorated function returns -the current execution thread, which can be stored and checked later, for -instance to verify that the thread ``.isAlive()``. - -Here is an example of usage. Suppose one wants to write some data to -an external resource which can be accessed by a single user at once -(for instance a printer). Then the access to the writing function must -be locked. Here is a minimalistic example: - -.. code-block:: python - - >>> async = decorator(Async(threading.Thread)) - - >>> datalist = [] # for simplicity the written data are stored into a list. - - >>> @async - ... def write(data): - ... # append data to the datalist by locking - ... with threading.Lock(): - ... time.sleep(1) # emulate some long running operation - ... datalist.append(data) - ... # other operations not requiring a lock here - -Each call to ``write`` will create a new writer thread, but there will -be no synchronization problems since ``write`` is locked. - -.. code-block:: python - - >>> write("data1") # doctest: +ELLIPSIS - <Thread(write-1, started...)> - - >>> time.sleep(.1) # wait a bit, so we are sure data2 is written after data1 - - >>> write("data2") # doctest: +ELLIPSIS - <Thread(write-2, started...)> - - >>> time.sleep(2) # wait for the writers to complete - - >>> print(datalist) - ['data1', 'data2'] - -contextmanager -------------------------------------- - -For a long time Python had in its standard library a ``contextmanager`` -decorator, able to convert generator functions into -``_GeneratorContextManager`` factories. For instance if you write - -.. code-block:: python - - >>> from contextlib import contextmanager - >>> @contextmanager - ... def before_after(before, after): - ... print(before) - ... yield - ... print(after) - - -then ``before_after`` is a factory function returning -``_GeneratorContextManager`` objects which can be used with -the ``with`` statement: - -.. code-block:: python - - >>> ba = before_after('BEFORE', 'AFTER') - >>> type(ba) - <class 'contextlib._GeneratorContextManager'> - >>> with ba: - ... 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 as in this example: - -.. code-block:: python - - >>> @ba # doctest: +SKIP - ... def hello(): - ... print('hello') - ... - >>> hello() # doctest: +SKIP - BEFORE - hello - AFTER - -The ``ba`` decorator is basically inserting a ``with ba:`` -block inside the function. -However there two issues: the first is that ``_GeneratorContextManager`` -objects are callable only in Python 3.2, so the previous example will break -in older versions of Python; the second is that -``_GeneratorContextManager`` objects do not preserve the signature -of the decorated functions: the decorated ``hello`` function here will have -a generic signature ``hello(*args, **kwargs)`` but will break when -called with more than zero arguments. For such reasons the decorator -module, starting with release 3.4, offers a ``decorator.contextmanager`` -decorator that solves both problems and works even in Python 2.5. -The usage is the same and factories decorated with ``decorator.contextmanager`` -will returns instances of ``ContextManager``, a subclass of -``contextlib._GeneratorContextManager`` with a ``__call__`` method -acting as a signature-preserving decorator. - -The ``FunctionMaker`` class ---------------------------------------------------------------- - -You may wonder about how the functionality of the ``decorator`` module -is implemented. The basic building block is -a ``FunctionMaker`` class which is able to generate on the fly -functions with a given name and signature from a function template -passed as a string. Generally speaking, you should not need to -resort to ``FunctionMaker`` when writing ordinary decorators, but -it is handy in some circumstances. You will see an example shortly, in -the implementation of a cool decorator utility (``decorator_apply``). - -``FunctionMaker`` provides a ``.create`` classmethod which -takes as input the name, signature, and body of the function -we want to generate as well as the execution environment -were the function is generated by ``exec``. Here is an example: - -.. code-block:: 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, so be careful with the ``%`` sign! - -``FunctionMaker.create`` also accepts keyword arguments and such -arguments are attached to the resulting function. This is useful -if you want to set some function attributes, for instance the -docstring ``__doc__``. - -For debugging/introspection purposes it may be useful to see -the source code of the generated function; to do that, just -pass the flag ``addsource=True`` and a ``__source__`` attribute will -be added to the generated function: - -.. code-block:: 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> - -``FunctionMaker.create`` can take as first argument a string, -as in the examples before, or a function. This is the most common -usage, since typically you want to decorate a pre-existing -function. A framework author may want to use directly ``FunctionMaker.create`` -instead of ``decorator``, since 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 (such use case is not made up; this is done in SQAlchemy -and in other frameworks). When the first argument of ``FunctionMaker.create`` -is a function, a ``FunctionMaker`` object is instantiated internally, -with attributes ``args``, ``varargs``, -``keywords`` and ``defaults`` which are the -the return values of the standard library function ``inspect.getargspec``. -For each argument in the ``args`` (which is a list of strings containing -the names of the mandatory arguments) an attribute ``arg0``, ``arg1``, -..., ``argN`` is also generated. Finally, there is a ``signature`` -attribute, a string with the signature of the original function. - -Notice that while I do not have plans -to change or remove the functionality provided in the -``FunctionMaker`` class, I do not guarantee that it will stay -unchanged forever. For instance, right now I am using the traditional -string interpolation syntax for function templates, but Python 2.6 -and Python 3.0 provide a newer interpolation syntax and I may use -the new syntax in the future. -On the other hand, the functionality provided by -``decorator`` has been there from version 0.1 and it is guaranteed to -stay there forever. - -Getting the source code ---------------------------------------------------- - -Internally ``FunctionMaker.create`` uses ``exec`` to generate the -decorated function. Therefore -``inspect.getsource`` will not work for decorated functions. That -means that the usual '??' trick in IPython will give you the (right on -the spot) message ``Dynamically generated function. No source code -available``. In the past I have considered this acceptable, since -``inspect.getsource`` does not really work even with regular -decorators. In that case ``inspect.getsource`` gives you the wrapper -source code which is probably not what you want: - -$$identity_dec - -.. code-block:: python - - @identity_dec - def example(): pass - - >>> print(inspect.getsource(example)) - def wrapper(*args, **kw): - return func(*args, **kw) - <BLANKLINE> - -(see bug report 1764286_ for an explanation of what is happening). -Unfortunately the bug is still there, even in Python 2.7 and 3.1. -There is however a workaround. The decorator module adds an -attribute ``.__wrapped__`` to the decorated function, containing -a reference to the original function. The easy way to get -the source code is to call ``inspect.getsource`` on the -undecorated function: - -.. code-block:: 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> - -.. _1764286: http://bugs.python.org/issue1764286 - -Dealing with third party decorators ------------------------------------------------------------------ - -Sometimes you find on the net some cool decorator that you would -like to include in your code. However, more often than not the cool -decorator is not signature-preserving. Therefore you may want 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 attribute ``.__wrapped__`` of the generated -function to the original function, so that you can get the right -source code. - -Notice that I am not providing this functionality in the ``decorator`` -module directly since I think it is best to rewrite the decorator rather -than adding an additional level of indirection. However, practicality -beats purity, so you can add ``decorator_apply`` to your toolbox and -use it if you need to. - -In order to give an example of usage of ``decorator_apply``, I will show a -pretty slick decorator that converts a tail-recursive function in an iterative -function. I have shamelessly stolen the basic idea 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 - -.. code-block:: python - - >>> print(factorial(4)) - 24 - -This decorator is pretty impressive, and should give you some food for -your mind ;) Notice that there is no recursion limit now, and 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 either returns a value without -making a recursive call, or returns directly the result of a recursive -call). - -Caveats and limitations -------------------------------------------- - -The first thing you should be aware of, it the fact that decorators -have a performance penalty. -The worse case is shown by the following example:: - - $ 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 MacBook, using the ``do_nothing`` decorator instead of the -plain function is more than three times slower:: - - $ bash performance.sh - 1000000 loops, best of 3: 0.669 usec per loop - 1000000 loops, best of 3: 0.181 usec per loop - -It should be noted that a real life function would probably do -something more useful than ``f`` here, and therefore in real life the -performance penalty could be completely negligible. As always, the -only way to know if there is -a penalty in your specific use case is to measure it. - -You should be aware that decorators will make your tracebacks -longer and more difficult to understand. Consider this example: - -.. code-block:: python - - >>> @trace - ... def f(): - ... 1/0 - -Calling ``f()`` will give you a ``ZeroDivisionError``, but since the -function is decorated the traceback will be longer: - -.. code-block:: 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* responsibile for the performance penalty, since is the -called *only once* at function decoration time, and not every time -the decorated function is called. - -At present, there is no clean way to avoid ``exec``. A clean solution -would require to change the CPython implementation of functions and -add an hook to make it possible to change their signature directly. -That could happen in future versions of Python (see PEP 362_) and -then the decorator module would become obsolete. However, at present, -even in Python 3.2 it is impossible to change the function signature -directly, therefore the ``decorator`` module is still useful. -Actually, this is one of the main reasons why I keep maintaining -the module and releasing new versions. - -.. _362: http://www.python.org/dev/peps/pep-0362 - -In the present implementation, decorators generated by ``decorator`` -can only be used on user-defined Python functions or methods, not on generic -callable objects, nor on built-in functions, due to limitations of the -``inspect`` module in the standard library. - -There is a restriction on the names of the arguments: for instance, -if try to call an argument ``_call_`` or ``_func_`` -you will get a ``NameError``: - -.. code-block:: 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 shares -the original function dictionary: - -.. code-block:: 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' - -Compatibility notes ---------------------------------------------------------------- - -Version 3.4 fixes some bugs in the support of recent versions of -Python 3. Version 3.3 was the first version of the ``decorator`` -module to fully support Python 3, including `function -annotations`_. Version 3.2 was the first version to support Python 3 -via the ``2to3`` conversion tool. The hard work (for me) has been -converting the documentation and the doctests. This has been possible -only after that docutils_ and pygments_ have been ported to Python 3. - -Version 3 of the ``decorator`` module do not contain any backward -incompatible change, apart from the removal of the functions -``get_info`` and ``new_wrapper``, which have been deprecated for -years. ``get_info`` has been removed since it was little used and -since it had to be changed anyway to work with Python 3.0; -``new_wrapper`` has been removed since it was useless: its major use -case (converting signature changing decorators to signature preserving -decorators) has been subsumed by ``decorator_apply``, whereas the other use -case can be managed with the ``FunctionMaker``. - -There are a few changes in the documentation: I removed the -``decorator_factory`` example, which was confusing some of my users, -and I removed the part about exotic signatures in the Python 3 -documentation, since Python 3 does not support them. - -Finally ``decorator`` cannot be used as a class decorator and the -`functionality introduced in version 2.3`_ has been removed. That -means that in order to define decorator factories with classes you -need to define the ``__call__`` method explicitly (no magic anymore). -All these changes should not cause any trouble, since they were -all rarely used features. Should you have any trouble, you can always -downgrade to the 2.3 version. - -The examples shown here have been tested with Python 2.6. Python 2.4 -is also supported - of course the examples requiring the ``with`` -statement will not work there. Python 2.5 works fine, but if you -run the examples in the interactive interpreter -you will notice a few differences since -``getargspec`` returns an ``ArgSpec`` namedtuple instead of a regular -tuple. That means that running the file -``documentation.py`` under Python 2.5 will print a few errors, but -they are not serious. - -.. _functionality introduced in version 2.3: http://www.phyast.pitt.edu/~micheles/python/documentation.html#class-decorators-and-decorator-factories -.. _function annotations: http://www.python.org/dev/peps/pep-3107/ -.. _distribute: http://packages.python.org/distribute/ -.. _docutils: http://docutils.sourceforge.net/ -.. _pygments: http://pygments.org/ - -LICENCE ---------------------------------------------- - -Copyright (c) 2005-2015, 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! -""" -from __future__ import with_statement -import sys -import threading -import time -import functools -import inspect -import itertools -from decorator import * -from functools import partial -from setup import VERSION - -today = time.strftime('%Y-%m-%d') - -__doc__ = __doc__.replace('$VERSION', VERSION).replace('$DATE', today) - - -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 decorated(%(signature)s)', - dict(decorated=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 decorator(_trace, f) - - -def on_success(result): # default implementation - "Called on the result of the function" - return result - - -def on_failure(exc_info): # default implementation - "Called if the function fails" - pass - - -def on_closing(): # default implementation - "Called at the end, both in case of success and failure" - pass - - -class Async(object): - """ - A decorator converting blocking functions into asynchronous - functions, by using threads or processes. Examples: - - async_with_threads = Async(threading.Thread) - async_with_processes = Async(multiprocessing.Process) - """ - - def __init__(self, threadfactory, on_success=on_success, - on_failure=on_failure, on_closing=on_closing): - self.threadfactory = threadfactory - self.on_success = on_success - self.on_failure = on_failure - self.on_closing = on_closing - - def __call__(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(): - try: - result = func(*args, **kw) - except: - self.on_failure(sys.exc_info()) - else: - return self.on_success(result) - finally: - self.on_closing() - thread = self.threadfactory(None, func_wrapper, name) - thread.start() - return thread - - -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.iteritems()) - else: - key = args - cache = func.cache - if key in cache: - return cache[key] - else: - cache[key] = result = func(*args, **kw) - return result - return functools.update_wrapper(memoize, func) - - -def _memoize(func, *args, **kw): - if kw: # frozenset is used to ensure hashability - key = args, frozenset(kw.iteritems()) - else: - key = args - cache = func.cache # attributed added by memoize - if key in cache: - return cache[key] - else: - cache[key] = result = func(*args, **kw) - return result - - -def memoize(f): - f.cache = {} - return decorator(_memoize, f) - - -def blocking(not_avail): - def blocking(f, *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 not_avail - elif f.thread.isAlive(): - return not_avail - else: # the thread is ended, return the stored result - del f.thread - return f.result - return decorator(blocking) - - -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" - - -def get_userclass(): - return User - - -class PermissionError(Exception): - pass - - -def restricted(user_class): - def restricted(func, *args, **kw): - "Restrict access to a given class of users" - userclass = get_userclass() - if issubclass(userclass, user_class): - return func(*args, **kw) - else: - raise PermissionError( - '%s does not have the permission to run %s!' - % (userclass.__name__, func.__name__)) - return decorator(restricted) - - -class Action(object): - """ - >>> a = Action() - >>> a.view() # ok - >>> a.insert() # doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - PermissionError: User does not have the permission to run insert! - """ - @restricted(User) - def view(self): - pass - - @restricted(PowerUser) - def insert(self): - pass - - @restricted(Admin) - def delete(self): - pass - - -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' - """ - - -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 - ... - >>> 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) |
