diff options
-rw-r--r-- | MANIFEST.in | 2 | ||||
-rw-r--r-- | PKG-INFO | 100 | ||||
-rw-r--r-- | README.txt | 80 | ||||
-rw-r--r-- | documentation.py | 1134 | ||||
-rw-r--r-- | documentation3.py | 1170 | ||||
-rw-r--r-- | setup.cfg | 5 | ||||
-rw-r--r-- | setup.py | 41 | ||||
-rw-r--r-- | src/decorator.egg-info/PKG-INFO | 100 | ||||
-rw-r--r-- | src/decorator.egg-info/SOURCES.txt | 11 | ||||
-rw-r--r-- | src/decorator.egg-info/dependency_links.txt | 1 | ||||
-rw-r--r-- | src/decorator.egg-info/not-zip-safe | 1 | ||||
-rw-r--r-- | src/decorator.egg-info/top_level.txt | 1 | ||||
-rw-r--r-- | src/decorator.py | 251 |
13 files changed, 2897 insertions, 0 deletions
diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..52e7659 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include documentation.py documentation3.py +exclude Makefile diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..8491af5 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,100 @@ +Metadata-Version: 1.1 +Name: decorator +Version: 3.4.0 +Summary: Better living through Python with decorators +Home-page: http://pypi.python.org/pypi/decorator +Author: Michele Simionato +Author-email: michele.simionato@gmail.com +License: BSD License +Description: Decorator module + ================= + + + :Author: Michele Simionato + :E-mail: michele.simionato@gmail.com + :Requires: Python 2.4+ + :Download page: http://pypi.python.org/pypi/decorator + :Installation: ``easy_install decorator`` + :License: BSD license + + Installation + ------------- + + If you are lazy, just perform + + $ easy_install decorator + + which will install just the module on your system. Notice that + Python 3 requires the easy_install version of the distribute_ project. + + If you prefer to install the full distribution from source, including + the documentation, download the tarball_, unpack it and run + + $ python setup.py install + + in the main directory, possibly as superuser. + + .. _tarball: http://pypi.python.org/pypi/decorator + .. _distribute: http://packages.python.org/distribute/ + + Testing + -------- + + For Python 2.5, 2.6, 2.7 run + + $ python documentation.py + + for Python 3.X run + + $ python documentation3.py + + You will see a few innocuous errors with Python 2.5, because some + inner details such as the introduction of the ArgSpec namedtuple and + Thread.__repr__ changed. You may safely ignore them. + + You cannot run the tests in Python 2.4, since there is a test using + the with statement, but the decorator module is expected to work + anyway (it has been used in production with Python 2.4 for years). My + plan is to keep supporting all Python versions >= 2.4 in the core + module, but I will keep the documentation and the tests updated only + for the latest Python versions in both the 2.X and 3.X branches. + + Finally, 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. + + Documentation + -------------- + + There are various versions of the documentation: + + - `HTML version (Python 2)`_ + - `PDF version (Python 2)`_ + + - `HTML version (Python 3)`_ + - `PDF version (Python 3)`_ + + .. _HTML version (Python 2): http://micheles.googlecode.com/hg/decorator/documentation.html + .. _PDF version (Python 2): http://micheles.googlecode.com/hg/decorator/documentation.pdf + .. _HTML version (Python 3): http://micheles.googlecode.com/hg/decorator/documentation3.html + .. _PDF version (Python 3): http://micheles.googlecode.com/hg/decorator/documentation3.pdf + + Repository + --------------- + + The project is hosted on GoogleCode as a Mercurial repository. You + can look at the source here: + + http://code.google.com/p/micheles/source/browse/#hg%2Fdecorator + +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 :: 3 +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Utilities diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..50b39c1 --- /dev/null +++ b/README.txt @@ -0,0 +1,80 @@ +Decorator module
+=================
+
+
+:Author: Michele Simionato
+:E-mail: michele.simionato@gmail.com
+:Requires: Python 2.4+
+:Download page: http://pypi.python.org/pypi/decorator
+:Installation: ``easy_install decorator``
+:License: BSD license
+
+Installation
+-------------
+
+If you are lazy, just perform
+
+$ easy_install decorator
+
+which will install just the module on your system. Notice that
+Python 3 requires the easy_install version of the distribute_ project.
+
+If you prefer to install the full distribution from source, including
+the documentation, download the tarball_, unpack it and run
+
+$ python setup.py install
+
+in the main directory, possibly as superuser.
+
+.. _tarball: http://pypi.python.org/pypi/decorator
+.. _distribute: http://packages.python.org/distribute/
+
+Testing
+--------
+
+For Python 2.5, 2.6, 2.7 run
+
+$ python documentation.py
+
+for Python 3.X run
+
+$ python documentation3.py
+
+You will see a few innocuous errors with Python 2.5, because some
+inner details such as the introduction of the ArgSpec namedtuple and
+Thread.__repr__ changed. You may safely ignore them.
+
+You cannot run the tests in Python 2.4, since there is a test using
+the with statement, but the decorator module is expected to work
+anyway (it has been used in production with Python 2.4 for years). My
+plan is to keep supporting all Python versions >= 2.4 in the core
+module, but I will keep the documentation and the tests updated only
+for the latest Python versions in both the 2.X and 3.X branches.
+
+Finally, 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.
+
+Documentation
+--------------
+
+There are various versions of the documentation:
+
+- `HTML version (Python 2)`_
+- `PDF version (Python 2)`_
+
+- `HTML version (Python 3)`_
+- `PDF version (Python 3)`_
+
+.. _HTML version (Python 2): http://micheles.googlecode.com/hg/decorator/documentation.html
+.. _PDF version (Python 2): http://micheles.googlecode.com/hg/decorator/documentation.pdf
+.. _HTML version (Python 3): http://micheles.googlecode.com/hg/decorator/documentation3.html
+.. _PDF version (Python 3): http://micheles.googlecode.com/hg/decorator/documentation3.pdf
+
+Repository
+---------------
+
+The project is hosted on GoogleCode as a Mercurial repository. You
+can look at the source here:
+
+ http://code.google.com/p/micheles/source/browse/#hg%2Fdecorator
diff --git a/documentation.py b/documentation.py new file mode 100644 index 0000000..3d5a5c0 --- /dev/null +++ b/documentation.py @@ -0,0 +1,1134 @@ +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: http://www.python.org/doc/2.5.2/lib/module-functools.html + +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) # I am using Python 2.6+ here + 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) + Traceback (most recent call last): + ... + TypeError: f1() takes exactly 1 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)) + +That includes even functions with exotic signatures like the following: + +.. code-block:: python + + >>> @trace + ... def exotic_signature((x, y)=(1,2)): return x+y + + >>> print getargspec(exotic_signature) + ArgSpec(args=[['x', 'y']], varargs=None, keywords=None, defaults=((1, 2),)) + >>> exotic_signature() + calling exotic_signature with args ((1, 2),), {} + 3 + +Notice that the support for exotic signatures has been deprecated +in Python 2.6 and removed in Python 3.0. + +``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): + ... print "calling %s with args %s, %s" % (f.func_name, args, kw) + ... 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 + python -m timeit -s " + from decorator import decorator + + @decorator + def do_nothing(func, *args, **kw): + return func(*args, **kw) + + @do_nothing + def f(): + pass + " "f()" + + python -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.995 usec per loop + 1000000 loops, best of 3: 0.273 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() + Traceback (most recent call last): + ... + File "<string>", line 2, in f + File "<doctest __main__[18]>", line 4, in trace + return f(*args, **kw) + File "<doctest __main__[47]>", line 3, in f + 1/0 + ZeroDivisionError: integer division or modulo by zero + +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.1 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. Moreover, notice +that you can decorate a method, but only before if becomes a bound or unbound +method, i.e. inside the class. +Here is an example of valid decoration: + +.. code-block:: python + + >>> class C(object): + ... @trace + ... def meth(self): + ... pass + +Here is an example of invalid decoration, when the decorator in +called too late: + +.. code-block:: python + + >>> class C(object): + ... def meth(self): + ... pass + ... + >>> trace(C.meth) + Traceback (most recent call last): + ... + TypeError: You are decorating a non function: <unbound method C.meth> + +The solution is to extract the inner function from the unbound method: + +.. code-block:: python + + >>> trace(C.meth.im_func) # doctest: +ELLIPSIS + <function meth at 0x...> + +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 +attribute ``.func_globals`` is a *copy* of the original function +attribute. Moreover the decorated function contains +a *copy* of the original function dictionary +(``vars(decorated_f) is not vars(f)``): + +.. 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.3 is 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 +invoked in the build process by the distribute_ project, the Python +3-compatible replacement of easy_install. 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-2012, 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, threading, time, functools, inspect, 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): + print "calling %s with args %s, %s" % (f.__name__, args, kw) + 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__, counter.next()) + 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() # err + 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 to avoid: + + >>> factorial.__doc__ + 'The good old factorial' + """ + +@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) + +if __name__ == '__main__': + import doctest; doctest.testmod() diff --git a/documentation3.py b/documentation3.py new file mode 100644 index 0000000..bd86cc6 --- /dev/null +++ b/documentation3.py @@ -0,0 +1,1170 @@ +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: http://www.python.org/doc/2.5.2/lib/module-functools.html + +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 contains +a *copy* of the original function dictionary +(``vars(decorated_f) is not vars(f)``): + +.. 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.3 is 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 +invoked in the build process by the distribute_ project, the Python +3-compatible replacement of easy_install. 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-2012, 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, threading, time, functools, inspect, 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() # err + 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) + +if __name__ == '__main__': + import doctest; doctest.testmod() diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..ebbec92 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[egg_info] +tag_build = +tag_svn_revision = 0 +tag_date = 0 + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..8190acd --- /dev/null +++ b/setup.py @@ -0,0 +1,41 @@ +try: + from setuptools import setup +except ImportError: + from distutils.core import setup +import os.path + +def getversion(fname): + """Get the __version__ reading the file: works both in Python 2.X and 3.X, + whereas direct importing would break in Python 3.X with a syntax error""" + for line in open(fname): + if line.startswith('__version__'): + return eval(line[13:]) + raise NameError('Missing __version__ in decorator.py') + +VERSION = getversion( + os.path.join(os.path.dirname(__file__), 'src/decorator.py')) + +if __name__ == '__main__': + setup(name='decorator', + version=VERSION, + description='Better living through Python with decorators', + long_description=open('README.txt').read(), + author='Michele Simionato', + author_email='michele.simionato@gmail.com', + url='http://pypi.python.org/pypi/decorator', + license="BSD License", + package_dir = {'': 'src'}, + py_modules = ['decorator'], + keywords="decorators generic utility", + platforms=["All"], + classifiers=['Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Topic :: Software Development :: Libraries', + 'Topic :: Utilities'], + use_2to3=True, + zip_safe=False) diff --git a/src/decorator.egg-info/PKG-INFO b/src/decorator.egg-info/PKG-INFO new file mode 100644 index 0000000..8491af5 --- /dev/null +++ b/src/decorator.egg-info/PKG-INFO @@ -0,0 +1,100 @@ +Metadata-Version: 1.1 +Name: decorator +Version: 3.4.0 +Summary: Better living through Python with decorators +Home-page: http://pypi.python.org/pypi/decorator +Author: Michele Simionato +Author-email: michele.simionato@gmail.com +License: BSD License +Description: Decorator module + ================= + + + :Author: Michele Simionato + :E-mail: michele.simionato@gmail.com + :Requires: Python 2.4+ + :Download page: http://pypi.python.org/pypi/decorator + :Installation: ``easy_install decorator`` + :License: BSD license + + Installation + ------------- + + If you are lazy, just perform + + $ easy_install decorator + + which will install just the module on your system. Notice that + Python 3 requires the easy_install version of the distribute_ project. + + If you prefer to install the full distribution from source, including + the documentation, download the tarball_, unpack it and run + + $ python setup.py install + + in the main directory, possibly as superuser. + + .. _tarball: http://pypi.python.org/pypi/decorator + .. _distribute: http://packages.python.org/distribute/ + + Testing + -------- + + For Python 2.5, 2.6, 2.7 run + + $ python documentation.py + + for Python 3.X run + + $ python documentation3.py + + You will see a few innocuous errors with Python 2.5, because some + inner details such as the introduction of the ArgSpec namedtuple and + Thread.__repr__ changed. You may safely ignore them. + + You cannot run the tests in Python 2.4, since there is a test using + the with statement, but the decorator module is expected to work + anyway (it has been used in production with Python 2.4 for years). My + plan is to keep supporting all Python versions >= 2.4 in the core + module, but I will keep the documentation and the tests updated only + for the latest Python versions in both the 2.X and 3.X branches. + + Finally, 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. + + Documentation + -------------- + + There are various versions of the documentation: + + - `HTML version (Python 2)`_ + - `PDF version (Python 2)`_ + + - `HTML version (Python 3)`_ + - `PDF version (Python 3)`_ + + .. _HTML version (Python 2): http://micheles.googlecode.com/hg/decorator/documentation.html + .. _PDF version (Python 2): http://micheles.googlecode.com/hg/decorator/documentation.pdf + .. _HTML version (Python 3): http://micheles.googlecode.com/hg/decorator/documentation3.html + .. _PDF version (Python 3): http://micheles.googlecode.com/hg/decorator/documentation3.pdf + + Repository + --------------- + + The project is hosted on GoogleCode as a Mercurial repository. You + can look at the source here: + + http://code.google.com/p/micheles/source/browse/#hg%2Fdecorator + +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 :: 3 +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Utilities diff --git a/src/decorator.egg-info/SOURCES.txt b/src/decorator.egg-info/SOURCES.txt new file mode 100644 index 0000000..4b7a116 --- /dev/null +++ b/src/decorator.egg-info/SOURCES.txt @@ -0,0 +1,11 @@ +MANIFEST.in +README.txt +documentation.py +documentation3.py +setup.py +src/decorator.py +src/decorator.egg-info/PKG-INFO +src/decorator.egg-info/SOURCES.txt +src/decorator.egg-info/dependency_links.txt +src/decorator.egg-info/not-zip-safe +src/decorator.egg-info/top_level.txt
\ No newline at end of file diff --git a/src/decorator.egg-info/dependency_links.txt b/src/decorator.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/decorator.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/src/decorator.egg-info/not-zip-safe b/src/decorator.egg-info/not-zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/decorator.egg-info/not-zip-safe @@ -0,0 +1 @@ + diff --git a/src/decorator.egg-info/top_level.txt b/src/decorator.egg-info/top_level.txt new file mode 100644 index 0000000..3fe18a4 --- /dev/null +++ b/src/decorator.egg-info/top_level.txt @@ -0,0 +1 @@ +decorator diff --git a/src/decorator.py b/src/decorator.py new file mode 100644 index 0000000..e003914 --- /dev/null +++ b/src/decorator.py @@ -0,0 +1,251 @@ +########################## LICENCE ############################### + +# Copyright (c) 2005-2012, Michele Simionato +# All rights reserved. + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: + +# Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# Redistributions in bytecode form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + +""" +Decorator module, see http://pypi.python.org/pypi/decorator +for the documentation. +""" + +__version__ = '3.4.0' + +__all__ = ["decorator", "FunctionMaker", "contextmanager"] + +import sys, re, inspect +if sys.version >= '3': + from inspect import getfullargspec + def get_init(cls): + return cls.__init__ +else: + class getfullargspec(object): + "A quick and dirty replacement for getfullargspec for Python 2.X" + def __init__(self, f): + self.args, self.varargs, self.varkw, self.defaults = \ + inspect.getargspec(f) + self.kwonlyargs = [] + self.kwonlydefaults = None + def __iter__(self): + yield self.args + yield self.varargs + yield self.varkw + yield self.defaults + def get_init(cls): + return cls.__init__.im_func + +DEF = re.compile('\s*def\s*([_\w][_\w\d]*)\s*\(') + +# basic functionality +class FunctionMaker(object): + """ + An object with the ability to create functions with a given signature. + It has attributes name, doc, module, signature, defaults, dict and + methods update and make. + """ + def __init__(self, func=None, name=None, signature=None, + defaults=None, doc=None, module=None, funcdict=None): + self.shortsignature = signature + if func: + # func can be a class or a callable, but not an instance method + self.name = func.__name__ + if self.name == '<lambda>': # small hack for lambda functions + self.name = '_lambda_' + self.doc = func.__doc__ + self.module = func.__module__ + if inspect.isfunction(func): + argspec = getfullargspec(func) + self.annotations = getattr(func, '__annotations__', {}) + for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs', + 'kwonlydefaults'): + setattr(self, a, getattr(argspec, a)) + for i, arg in enumerate(self.args): + setattr(self, 'arg%d' % i, arg) + if sys.version < '3': # easy way + self.shortsignature = self.signature = \ + inspect.formatargspec( + formatvalue=lambda val: "", *argspec)[1:-1] + else: # Python 3 way + allargs = list(self.args) + allshortargs = list(self.args) + if self.varargs: + allargs.append('*' + self.varargs) + allshortargs.append('*' + self.varargs) + elif self.kwonlyargs: + allargs.append('*') # single star syntax + for a in self.kwonlyargs: + allargs.append('%s=None' % a) + allshortargs.append('%s=%s' % (a, a)) + if self.varkw: + allargs.append('**' + self.varkw) + allshortargs.append('**' + self.varkw) + self.signature = ', '.join(allargs) + self.shortsignature = ', '.join(allshortargs) + self.dict = func.__dict__.copy() + # func=None happens when decorating a caller + if name: + self.name = name + if signature is not None: + self.signature = signature + if defaults: + self.defaults = defaults + if doc: + self.doc = doc + if module: + self.module = module + if funcdict: + self.dict = funcdict + # check existence required attributes + assert hasattr(self, 'name') + if not hasattr(self, 'signature'): + raise TypeError('You are decorating a non function: %s' % func) + + def update(self, func, **kw): + "Update the signature of func with the data in self" + func.__name__ = self.name + func.__doc__ = getattr(self, 'doc', None) + func.__dict__ = getattr(self, 'dict', {}) + func.func_defaults = getattr(self, 'defaults', ()) + func.__kwdefaults__ = getattr(self, 'kwonlydefaults', None) + func.__annotations__ = getattr(self, 'annotations', None) + callermodule = sys._getframe(3).f_globals.get('__name__', '?') + func.__module__ = getattr(self, 'module', callermodule) + func.__dict__.update(kw) + + def make(self, src_templ, evaldict=None, addsource=False, **attrs): + "Make a new function from a given template and update the signature" + src = src_templ % vars(self) # expand name and signature + evaldict = evaldict or {} + mo = DEF.match(src) + if mo is None: + raise SyntaxError('not a valid function template\n%s' % src) + name = mo.group(1) # extract the function name + names = set([name] + [arg.strip(' *') for arg in + self.shortsignature.split(',')]) + for n in names: + if n in ('_func_', '_call_'): + raise NameError('%s is overridden in\n%s' % (n, src)) + if not src.endswith('\n'): # add a newline just for safety + src += '\n' # this is needed in old versions of Python + try: + code = compile(src, '<string>', 'single') + # print >> sys.stderr, 'Compiling %s' % src + exec code in evaldict + except: + print >> sys.stderr, 'Error in generated code:' + print >> sys.stderr, src + raise + func = evaldict[name] + if addsource: + attrs['__source__'] = src + self.update(func, **attrs) + return func + + @classmethod + def create(cls, obj, body, evaldict, defaults=None, + doc=None, module=None, addsource=True, **attrs): + """ + Create a function from the strings name, signature and body. + evaldict is the evaluation dictionary. If addsource is true an attribute + __source__ is added to the result. The attributes attrs are added, + if any. + """ + if isinstance(obj, str): # "name(signature)" + name, rest = obj.strip().split('(', 1) + signature = rest[:-1] #strip a right parens + func = None + else: # a function + name = None + signature = None + func = obj + self = cls(func, name, signature, defaults, doc, module) + ibody = '\n'.join(' ' + line for line in body.splitlines()) + return self.make('def %(name)s(%(signature)s):\n' + ibody, + evaldict, addsource, **attrs) + +def decorator(caller, func=None): + """ + decorator(caller) converts a caller function into a decorator; + decorator(caller, func) decorates a function using a caller. + """ + if func is not None: # returns a decorated function + evaldict = func.func_globals.copy() + evaldict['_call_'] = caller + evaldict['_func_'] = func + return FunctionMaker.create( + func, "return _call_(_func_, %(shortsignature)s)", + evaldict, undecorated=func, __wrapped__=func) + else: # returns a decorator + if inspect.isclass(caller): + name = caller.__name__.lower() + callerfunc = get_init(caller) + doc = 'decorator(%s) converts functions/generators into ' \ + 'factories of %s objects' % (caller.__name__, caller.__name__) + fun = getfullargspec(callerfunc).args[1] # second arg + elif inspect.isfunction(caller): + name = '_lambda_' if caller.__name__ == '<lambda>' \ + else caller.__name__ + callerfunc = caller + doc = caller.__doc__ + fun = getfullargspec(callerfunc).args[0] # first arg + else: # assume caller is an object with a __call__ method + name = caller.__class__.__name__.lower() + callerfunc = caller.__call__.im_func + doc = caller.__call__.__doc__ + fun = getfullargspec(callerfunc).args[1] # second arg + evaldict = callerfunc.func_globals.copy() + evaldict['_call_'] = caller + evaldict['decorator'] = decorator + return FunctionMaker.create( + '%s(%s)' % (name, fun), + 'return decorator(_call_, %s)' % fun, + evaldict, undecorated=caller, __wrapped__=caller, + doc=doc, module=caller.__module__) + +######################### contextmanager ######################## + +def __call__(self, func): + 'Context manager decorator' + return FunctionMaker.create( + func, "with _self_: return _func_(%(shortsignature)s)", + dict(_self_=self, _func_=func), __wrapped__=func) + +try: # Python >= 3.2 + + from contextlib import _GeneratorContextManager + ContextManager = type( + 'ContextManager', (_GeneratorContextManager,), dict(__call__=__call__)) + +except ImportError: # Python >= 2.5 + + from contextlib import GeneratorContextManager + def __init__(self, f, *a, **k): + return GeneratorContextManager.__init__(self, f(*a, **k)) + ContextManager = type( + 'ContextManager', (GeneratorContextManager,), + dict(__call__=__call__, __init__=__init__)) + +contextmanager = decorator(ContextManager) |