The power and beauty of decorators

    One of the most difficult to understand and understand elements of the language is the decorator , although in fact it is a very simple thing, accessible to understanding even by a novice programmer. I do not open new Everests, but only offer a brief overview of the possibilities and some typical examples of use. A sort of short digression into metaprogramming in python.

    Upd 1 : changed a somewhat categorical statement about the dissimilarity of the Decorator pattern and the language design of the same name to a softer one.


    At the very beginning, I would like to note that the decorator considered here as an element of the Python language is not an implementation of the design pattern of the same name , its capabilities are much wider, although the pattern itself can be implemented through the Python decorator.


    What is a decorator and the simplest ways to use it

    So, a decorator is a convenient way to change the behavior of some function (and since Python 2.6 and 3.0 and the whole class). From the point of view of syntax it looks quite simple. For example, the following code fragment using a decorator:

    @ f1
    def func (x): pass


    equivalent to this:

    def func (x): pass
    func = f1 (func)


    The word “equivalent” needs to be understood literally: the operation is performed at the time the function is defined once and if f1 returns, say, None, then None will be written in the func variable. A simple example (the decorating function returns None, as a result, func is also equal to None):

    def empty (f):
        return none
    @empty
    def func (x, y):
        return x + y
    print func # will print: None
    


    Let's look at a more practical example. Suppose you need to check how quickly a certain function works, you need to know how much time each call takes. The task is easily solved with the help of a decorator.

    import time
    def timer (f):
        def tmp (* args, ** kwargs):
            t = time.time ()
            res = f (* args, ** kwargs)
            print "Function execution time:% f"% (time.time () - t)
            return res
        return tmp
    @timer
    def func (x, y):
        return x + y
    func (1, 2) # will print something like: Function execution time: 0.0004


    As you can see from the example, to force the func function to print the run time at each execution, just “wrap” it in the timer decorator. Comment out the line “@timer” and func continues to work as usual.

    The timer () function is the most typical decorator. As its only parameter, it takes a function, internally creates a new function (in our example with the name tmp), in which it adds some logic and returns this very new function. Pay attention to the signature of the tmp () function -  tmp (* args, ** kwargs) , this is the standard way to “capture” all possible arguments, so our decorator is suitable for functions with a completely arbitrary signature.

    The function can be wrapped in several decorators. In this case, they are “executed” from top to bottom. For example, create a pause () decorator that pauses for one second before executing a function.

    import time
    def pause (f):
        def tmp (* args, ** kwargs):
            time.sleep (1)
            return f (* args, ** kwargs)
        return tmp
    


    And we define the func function as follows (using two decorators at once - pause and timer):

    @timer
    @pause
    def func (x, y):
        return x + y


    Now calling func (1, 2) will show the total execution time of approximately one second.

    More sophisticated use of decorators


    You might think that only a function can be used as a decorator. This is not true. Any object that can be called up can act as a decorator. For example, a class may act as a decorator. Here is a much more complex example showing how threads can be constructed using decorators:

    import threading
    class Thread (threading.Thread):
        def __init __ (self, f):
            threading.Thread .__ init __ (self)
            self.run = f
    @Thread
    def ttt ():
        print "This is a thread function"
    ttt.start ()


    Let's look at this example in detail. The “classic” way to create a thread class is as follows: a new class is created, the heir to the threading.Thread class (threading is a standard Python module for working with threads); in the class, the run () method is set, which directly puts the code that needs to be executed in a separate thread, then an instance of this class is created and the start () method is called for it. Here is what it would look like in the “classic” version:

    class ThreadClassic (threading.Thread):
        def run (self):
            print "This is a thread function"
    ttt = ThreadClassic ()
    ttt.start ()


    In our case, the decorated function is passed as an argument to the constructor of the stream class, where it is assigned to the component of the run class.

    To create several different threads, you need to duplicate the "classic" code twice. And when using "streaming" decorators - just add a call to the decorator to the stream function.

    The stream example is for informational purposes only. In reality, it must be used very carefully, since not all streaming code can be wrapped in the decorator described here.


    You can pass parameters to the decorator, a record of the form:

    @ f1 (123)
    def func (): pass

    equivalent to

    def func (): pass
    func = f1 (123) (func)
    


    In essence, this means that the decorator is the result of executing the function f1 (123). Let's write an updated pause () decorator, which allows you to specify the amount of pause before executing the wrapped function:

    import time
    def pause (t):
        def wrapper (f):
            def tmp (* args, ** kwargs):
                time.sleep (t)
                return f (* args, ** kwargs)
            return tmp
        return wrapper
    @pause (4)
    def func (x, y):
        return x + y
    print func (1, 2)

    Notice how the decorator is actually created dynamically inside the pause () function.

    Using decorators in classes


    Using decorators on class methods is no different than using decorators on regular functions. However, classes have predefined decorators named staticmethod and classmethod . They are intended to define static methods and class methods, respectively. Here is an example of their use:

    class TestClass (object):
        @classmethod
        def f1 (cls):
            print cls .__ name__
        @staticmethod
        def f2 ():
            pass
    class TestClass2 (TestClass):
        pass
    TestClass.f1 () # prints TestClass
    TestClass2.f1 () # prints TestClass2
    a = TestClass2 ()
    a.f1 () # prints TestClass2
    


    The static method (wrapped by the staticmethod decorator) basically corresponds to static methods in C ++ or Java. But the class method is something more interesting. The first argument to such a method is a class (not an instance!), This happens in much the same way as with ordinary methods, which receive a reference to the class instance as the first argument. In the case when the class method is called on the instance, the current instance class is passed as the first parameter, this can be seen in the example above: for the generated class, the generated class is transferred.

    Where else can decorators be used?

    The list of potential applications for decorators is very long:

    Real difficulties


    You need to use decorators very carefully, well aware of what exactly you want to achieve. Excessive use of them leads to the appearance of code that is too complicated to understand. You can write in an attack of insight such that you yourself will not understand later how the written works.

    Using a decorator breaks documentation strings for a method / function. The problem can be solved by manually “forwarding” the __doc__ value to the function created inside the decorator. And you can use the wonderful module with the unexpected name decorator , which, in addition to supporting doc strings, can do many other useful things.

    Recommended Reading

    1. http://www.phyast.pitt.edu/~micheles/python/documentation.html - decorator module documentation page
    2. www.ibm.com/developerworks/linux/library/l-cpdecor.html - article about decorators on IBM developerWorks (and translation into Russian)
    3. Mark Lutz: Learning Python, 3rd Edition, Chapter “Function Decorators”
    4. PEP 318: Function Decorators
    5. PEP 3129: Class Decorators (since Python 2.6 and Python 3.0)
      wiki.python.org/moin/PythonDecorators - an article from the official Python wiki about decorators

    Also popular now: