Explain the use of decorators.

Decorators in Python are used to modify or inject code in functions or classes. Using decorators, you can wrap a class or function method call so that a piece of code can be executed before or after the execution of the original code. Decorators can be used to check for permissions, modify or track the arguments passed to a method, logging the calls to a specific method, etc.

Add Comment


  • 8 Answer(s)

    Python Decorators – Key Takeaways. Decorators define reusable building blocks you can apply to a callable to modify its behavior without permanently modifying the callable itself. The @ syntax is just a shorthand for calling the decorator on an input function.

    Answered on March 7, 2019.
    Add Comment

    Decorators belong most probably to the most beautiful and most powerful design possibilities in Python, but at the same time the concept is considered by many as complicated to get into. To be precise, the usage of decorates is very easy, but writing decorators can be complicated, especially if you are not experienced with decorators and some functional programming concepts.

    Even though it is the same underlying concept, we have two different kinds of decorators in Python:

    • Function decorators
    • Class decorators

    A decorator in Python is any callable Python object that is used to modify a function or a class. A reference to a function “func” or a class “C” is passed to a decorator and the decorator returns a modified function or class. The modified functions or classes usually contain calls to the original function “func” or class “C”.

    You may also consult our chapter on memoization with decorators.

    If you like the image on the right side of this page and if you are also interested in image processing with Python, Numpy, Scipy and Matplotlib, you will definitely like our chapter on Image Processing Techniques, it explains the whole process of the making-of of our decorator and at sign picture!

    Answered on March 7, 2019.
    Add Comment

    First Steps to Decorators

    We know from our various Python training classes that there are some sticking points in the definitions of decorators, where many beginners get stuck.

    Therefore, we wil will introduce decorators by repeating some important aspects of functions. First you have to know or remember that function names are references to functions and that we can assign multiple names to the same function:

    >>> def succ(x):
    ...     return x + 1
    ... 
    >>> successor = succ
    >>> successor(10)
    11
    >>> succ(10)
    11
    

    This means that we have two names, i.e. “succ” and “successor” for the same function. The next important fact is that we can delete either “succ” or “successor” without deleting the function itself.

    >>> del succ
    >>> successor(10)
    11
    Answered on March 7, 2019.
    Add Comment

    A Simple Decorator

    Now we have everything ready to define our first simple decorator:

     
    def our_decorator(func):
        def function_wrapper(x):
            print("Before calling " + func.__name__)
            func(x)
            print("After calling " + func.__name__)
        return function_wrapper
    
    def foo(x):
        print("Hi, foo has been called with " + str(x))
    
    print("We call foo before decoration:")
    foo("Hi")
        
    print("We now decorate foo with f:")
    foo = our_decorator(foo)
    
    print("We call foo after decoration:")
    foo(42)
    

    If you look at the following output of the previous program, you can see what’s going on. After the decoration “foo = our_decorator(foo)”, foo is a reference to the ‘function_wrapper’. ‘foo’ will be called inside of ‘function_wrapper’, but before and after the call some additional code will be executed, i.e. in our case two print functions.

     
    We call foo before decoration:
    Hi, foo has been called with Hi
    We now decorate foo with f:
    We call foo after decoration:
    Before calling foo
    Hi, foo has been called with 42
    After calling foo
    Answered on March 7, 2019.
    Add Comment

    The Usual Syntax for Decorators in Python

    The decoration in Python is usually not performed in the way we did it in our previous example, even though the notation foo = our_decorator(foo) is catchy and easy to grasp. This is the reason, why we used it! You can also see a design problem in our previous approach. “foo” existed in the same program in two versions, before decoration and after decoration.

    We will do a proper decoration now. The decoration occurrs in the line before the function header. The “@” is followed by the decorator function name.

    We will rewrite now our initial example. Instead of writing the statement

    foo = our_decorator(foo)

    we can write

    @our_decorator

    But this line has to be directly positioned in front of the decorated function. The complete example looks like this now:

    def our_decorator(func):
        def function_wrapper(x):
            print("Before calling " + func.__name__)
            func(x)
            print("After calling " + func.__name__)
        return function_wrapper
    
    @our_decorator
    def foo(x):
        print("Hi, foo has been called with " + str(x))
    
    foo("Hi")
    

    We can decorate every other function which takes one parameter with our decorator ‘our_decorator’. We demonstrate this in the following. We have slightly changed our function wrapper, so that we can see the result of the function calls:

    def our_decorator(func):
        def function_wrapper(x):
            print("Before calling " + func.__name__)
            res = func(x)
            print(res)
            print("After calling " + func.__name__)
        return function_wrapper
    
    @our_decorator
    def succ(n):
        return n + 1
    
    succ(10)
    

    The output of the previous program:

    Before calling succ
    11
    After calling succ
    

    It is also possible to decorate third party functions, e.g. functions we import from a module. We can’t use the Python syntax with the “at” sign in this case:

     
    from math import sin, cos
    
    def our_decorator(func):
        def function_wrapper(x):
            print("Before calling " + func.__name__)
            res = func(x)
            print(res)
            print("After calling " + func.__name__)
        return function_wrapper
    
    sin = our_decorator(sin)
    cos = our_decorator(cos)
    
    for f in [sin, cos]:
        f(3.1415)
    

    We get the following output:

     
    Before calling sin
    9.265358966049026e-05
    After calling sin
    Before calling cos
    -0.9999999957076562
    After calling cos
    

    Summarizing we can say that a decorator in Python is a callable Python object that is used to modify a function, method or class definition. The original object, the one which is going to be modified, is passed to a decorator as an argument. The decorator returns a modified object, e.g. a modified function, which is bound to the name used in the definition.

    The previous function_wrapper works only for functions with exactly one parameter. We provide a generalized version of the function_wrapper, which accepts functions with arbitrary parameters in the following example:

     
    from random import random, randint, choice
    
    def our_decorator(func):
        def function_wrapper(*args, **kwargs):
            print("Before calling " + func.__name__)
            res = func(*args, **kwargs)
            print(res)
            print("After calling " + func.__name__)
        return function_wrapper
    
    random = our_decorator(random)
    randint = our_decorator(randint)
    choice = our_decorator(choice)
    
    random()
    randint(3, 8)
    choice([4, 5, 6])
    

    The result looks as expected:

     
    Before calling random
    0.16420183945821654
    After calling random
    Before calling randint
    8
    After calling randint
    Before calling choice
    5
    After calling choice
    Answered on March 7, 2019.
    Add Comment

    Counting Function Calls with Decorators

    The following example uses a decorator to count the number of times a function has been called. To be precise, we can use this decorator solely for functions with exactly one parameter:

    def call_counter(func):
        def helper(x):
            helper.calls += 1
            return func(x)
        helper.calls = 0
    
        return helper
    
    @call_counter
    def succ(x):
        return x + 1
    
    print(succ.calls)
    for i in range(10):
        succ(i)
        
    print(succ.calls)
    

    The output looks like this:

    0
    10
    

    We pointed out that we can use our previous decorator only for functions, which take exactly one parameter. We will use the *args and **kwargs notation to write decorators which can cope with functions with an arbitrary number of positional and keyword parameters.

    def call_counter(func):
        def helper(*args, **kwargs):
            helper.calls += 1
            return func(*args, **kwargs)
        helper.calls = 0
    
        return helper
    
    @call_counter
    def succ(x):
        return x + 1
    
    @call_counter
    def mul1(x, y=1):
        return x*y + 1
    
    print(succ.calls)
    for i in range(10):
        succ(i)
    mul1(3, 4)
    mul1(4)
    mul1(y=3, x=2)
        
    print(succ.calls)
    print(mul1.calls)
    

    The output looks like this:

    0
    10
    3
    
    Answered on March 7, 2019.
    Add Comment

    Decorators with Parameters

    We define two decorators in the following code:

    def evening_greeting(func):
        def function_wrapper(x):
            print("Good evening, " + func.__name__ + " returns:")
            func(x)
        return function_wrapper
    
    def morning_greeting(func):
        def function_wrapper(x):
            print("Good morning, " + func.__name__ + " returns:")
            func(x)
        return function_wrapper
    
    @evening_greeting
    def foo(x):
        print(42)
    
    foo("Hi")
    

    These two decorators are nearly the same, except for the greeting. We want to add a parameter to the decorator to be capable of customizing the greeting, when we do the decoration. We have to wrap another function around our previous decorator function to accomplish this. We can now easy say “Good Morning” in the Greek way:

    def greeting(expr):
        def greeting_decorator(func):
            def function_wrapper(x):
                print(expr + ", " + func.__name__ + " returns:")
                func(x)
            return function_wrapper
        return greeting_decorator
    
    @greeting("καλημερα")
    def foo(x):
        print(42)
    
    foo("Hi")
    

    The output:

    καλημερα, foo returns:
    42
    

    If we don’t want or cannot use the “at” decorator syntax, we can do it with function calls:

    def greeting(expr):
        def greeting_decorator(func):
            def function_wrapper(x):
                print(expr + ", " + func.__name__ + " returns:")
                func(x)
            return function_wrapper
        return greeting_decorator
    
    
    def foo(x):
        print(42)
    
    greeting2 = greeting("καλημερα")
    foo = greeting2(foo)
    foo("Hi")
    

    The result is the same as before:

    καλημερα, foo returns:
    42
    

    Of course, we don’t need the additional definition of “greeting2”. We can directly apply the result of the call “greeting(“καλημερα”)” on “foo”:

    foo = greeting("καλημερα")(foo)
    
    Answered on March 7, 2019.
    Add Comment

    Using a Class as a Decorator

    We will rewrite the following decorator as a class:

    def decorator1(f):
        def helper():
            print("Decorating", f.__name__)
            f()
        return helper
    
    @decorator1
    def foo():
        print("inside foo()")
    
    foo()
    

    The following decorator implemented as a class does the same “job”:

    class decorator2:
        
        def __init__(self, f):
            self.f = f
            
        def __call__(self):
            print("Decorating", self.f.__name__)
            self.f()
    
    @decorator2
    def foo():
        print("inside foo()")
    
    foo()
    

    Both versions return the same output:

    Decorating foo
    inside foo()
    Answered on March 7, 2019.
    Add Comment


  • Your Answer

    By posting your answer, you agree to the privacy policy and terms of service.