📜 ⬆️ ⬇️

Strength and beauty of decorators

One of the most difficult to understand and comprehend elements of the language is the decorator , although in fact it is a very simple thing that can be understood even by a novice programmer. New Everest I do not open, but only offer a brief overview of the possibilities and a few typical examples of use. A sort of a short excursion into python metaprogramming.

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


At the very beginning, it should be noted 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 possibilities are much broader, although the pattern itself can be implemented through the Python decorator.

')
What is a decorator and the simplest ways to use it

So, the decorator is a convenient way to change the behavior of a certain function (and since Python 2.6 and 3.0 and the whole class). In terms of syntax, it looks quite simple. For example, the following code snippet using the decorator:

 @ f1
 def func (x): pass 


equivalent to this:

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


The word “equivalent” must be understood literally: the operation is performed at the time of the function definition once and if f1 returns, say, None, then the variable func will contain None. A simple example (decorating function returns None, in the end func also turns out to be None):

  def empty (f):
     return none

 @empty
 def func (x, y):
     return x + y

 print func # prints: None


Let's look at a more practical example. Suppose you need to check how quickly some function works, you need to know how long each call takes. The task is simply 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) # prints something like: Function execution time: 0.0004 


As you can see from the example, in order to force the function func to print the operation time with each execution, it is enough to “wrap” it into the timer decorator. Let's 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 accepts a function, internally creates a new function (in our example with the name tmp), in which it adds some logic and returns this newest 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 will pause for one second before executing the 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 the func (1, 2) call will show the total execution time in about one second.

More complex 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” can act as a decorator. For example, a class may act as a decorator. Here is a much more complicated example showing how you can construct threads with the help of 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 take a look at this example in detail. The “classic” way to create a thread class is the following: a new class is created, a successor to the threading.Thread class (threading is a standard Python module for working with threads); in the class, the run () method is set, in which the code itself is placed, which 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 how it would look 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 run class component.

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

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


In the decorator you can pass the parameters, record type:

  @ f1 (123)
 def func (): pass 

is equivalent to

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


In essence, this means that the decorator is the result of the execution of 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 classrooms


The use of decorators on class methods is no different from the use of decorators on ordinary functions. However, for classes there are predefined decorators named staticmethod and classmethod . They are designed to set 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 () # types TestClass
 TestClass2.f1 () # prints TestClass2

 a = TestClass2 ()
 a.f1 () # types 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 for such a method is a class (not an instance!), This happens in much the same way as with conventional methods, which receive a reference to an instance of a class with the first argument. In the case when a class method is called on an instance, the actual parameter of the instance is passed as the first parameter, as shown in the example above: for the generated class, the generated class is passed.

Where else can decorators be used

The list of potential applications for decorators is very large:

Real difficulties


It is necessary to use decorators very carefully, well aware of what exactly you want to achieve. Excessive use of them leads to code that is too difficult to understand. You can write in a fit of insight, so that later you will not understand how it works.

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

Recommended literature

  1. http://www.phyast.pitt.edu/~micheles/python/documentation.html - decorator module documentation page
  2. www.ibm.com/developerworks/linux/library/l-cpdecor.html - an 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

Source: https://habr.com/ru/post/46306/


All Articles