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
- http://www.phyast.pitt.edu/~micheles/python/documentation.html - decorator module documentation page
- www.ibm.com/developerworks/linux/library/l-cpdecor.html - an article about decorators on IBM developerWorks (and translation into Russian)
- Mark Lutz: Learning Python, 3rd Edition, Chapter “Function Decorators”
- PEP 318: Function Decorators
- 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