How To Use Python Decorator With Examples

Python decorator is essentially a python function, which allows other functions to add additional features without any code changes. The return value of the decorator is also a function object. In short, a decorator is a function that returns a function.

1. Scenarios Where Python Decorators Are Used.

  1. Often used in scenarios with aspect-oriented programming requirements, such as: inserting logs, performance testing, transaction processing, caching, permission verification, etc.
  2. Python decorator is an excellent design to solve this kind of problem. With decorator, we can extract a lot of similar code that has nothing to do with the function itself and continue to reuse those code.
  3. Generally speaking, the function of the decorator is to add additional functions to existing objects.

2. Why Do We Need Decorators.

  1. Let’s start with a simple example.
    def foo():
    print('i am foo')
  2. Now there is a new requirement, hoping to record the execution log of the function, so add the log code to the code.
    def foo():
        print('i am foo')
        print("foo is running")
  3. Suppose there are 100 functions that need to add this requirement, and the requirement of printing logs before execution may be added to another 100 functions in the future, what should you do? Do you change them one by one? Of course not. This will cause a lot of similar code. In order to reduce repeated code writing, we can define a function to deal with the log, and then execute the real business code after the log is processed.
  4. In the following example, the function use_logging is a decorator. It wraps the function func that executes real business methods. It looks like the function bar is decorated by the function used_logging.
    def use_logging(func):
        print("%s is running" % func.__name__)
        func()
    
    def bar():
        print('i am bar')
    
    use_logging(bar)
    # Below is the above code execution result.
    # bar is running
    # i am bar
  5. In the above example, the entry and exit of a function are called an Aspect. This kind of programming is called aspect-oriented Programming.
  6. Through the above use_logging function, we have added the log feature. No matter how many functions need to add the log feature or modify the format of the log in the future, we only need to modify the use_logging function and decorate the target function with the use_logging  decorator.
    def use_logging(func):
        print("%s is running" % func.__name__)
        return func
    
    @use_logging
    def bar():
        print('i am bar')
    
    bar()

3. Getting Started With Basic Decorators.

3.1 Decorator Syntax.

  1. Python provides @ symbol as syntax sugar of decorator, which makes it more convenient for us to apply decorating function.
  2. However, using syntax sugar requires that decorating function must return a function object.
  3. So we wrap the func() function above with an embedded function and return it.
  4. The decorator first performs the decorating function use_ logging(), and then returns the decorated function bar().
  5. Therefore, when bar() is called, it is equivalent to executing two functions, equivalent to use_ logging(bar)().
    def use_logging(func):
        def _deco():
            print("%s is running" % func.__name__)
            func()
        return _deco
    
    @use_logging
    def bar():
        print('i am bar')
    
    bar()

3.2 Decorating Functions That Has Parameters.

  1. Now we need to pass in two parameters and calculate the value, so we need to change the inner function and pass in two parameters a and b, which are equivalent to use_ logging(bar)(1,2).
    def use_logging(func):
        def _deco(a,b):
            print("%s is running" % func.__name__)
            func(a,b)
        return _deco
    
    @use_logging
    def bar(a,b):
        print('i am bar:%s'%(a+b))
    
    bar(1,2)
  2. The number and type of parameters of the function we decorate are different. Do we need to modify the decorator every time?
  3. Of course, this is unreasonable, so we use Python’s parameters * args and * * kwargs to solve the parameter problem.

3.3 How To Fix The Number Of Function Parameters Is Uncertain.

  1. With the following modifications, we have adapted to various length and type parameters. This version of the decorator can decorate any type of parameterless function.
    def use_logging(func):
        def _deco(*args,**kwargs):
            print("%s is running" % func.__name__)
            func(*args,**kwargs)
        return _deco
    
    @use_logging
    def bar(a,b):
        print('i am bar:%s'%(a+b))
    @use_logging
    def foo(a,b,c):
        print('i am bar:%s'%(a+b+c))
    
    bar(1,2)
    foo(1,2,3)

3.4 Decorator With Parameters.

  1. In some cases, we need to pass parameters to the decorator, so we need to write a higher-order function that returns a decorator, which will be more complicated. Below is an example.
    #! /usr/bin/env python
    # -*- coding:utf-8 -*-
    # __author__ = "TKQ"
    
    def use_logging(level):
        def _deco(func):
            def __deco(*args, **kwargs):
                if level == "warn":
                    print "%s is running" % func.__name__
                return func(*args, **kwargs)
            return __deco
        return _deco
    
    @use_logging(level="warn")
    def bar(a,b):
        print('i am bar:%s'%(a+b))
    
    bar(1,3)
    
    # equals to use_logging(level="warn")(bar)(1,3)

3.5 functools.wraps.

  1. The use of decorators greatly reuses the code, but it has a disadvantage that the meta-information of the original function is missing, such as the docstring, __name__, and parameter list of the function. Look at the example first.
    def use_logging(func):
        def _deco(*args,**kwargs):
            print("%s is running" % func.__name__)
            func(*args,**kwargs)
        return _deco
    
    @use_logging
    def bar():
        print('i am bar')
        print(bar.__name__)
    
    bar()
    
    #bar is running
    #i am bar
    #_deco
    # The function name is changed to _deco instead of bar, which can cause problems when using the reflection feature. So functools.wraps was introduced to solve this problem.
  2. We can use functools.wraps to solve this problem like below.
    import functools
    def use_logging(func):
        @functools.wraps(func)
        def _deco(*args,**kwargs):
            print("%s is running" % func.__name__)
            func(*args,**kwargs)
        return _deco
    
    @use_logging
    def bar():
        print('i am bar')
        print(bar.__name__)
    
    
    bar()
    
    #result:
    #bar is running
    #i am bar
    #bar  ,This result is what we want.

3.6 Implement the decorator adaptive function with and without parameters.

  1. Below source code can make the decorator auto check with and without parameters.
    import functools
    
    def use_logging(arg):
        if callable(arg):# Check whether the argument passed in is a function or not, and call this branch if the decorator has no arguments.
            @functools.wraps(arg)
            def _deco(*args,**kwargs):
                print("%s is running" % arg.__name__)
                arg(*args,**kwargs)
            return _deco
        else:# The decorator with arguments calls this branch.
            def _deco(func):
                @functools.wraps(func)
                def __deco(*args, **kwargs):
                    if arg == "warn":
                        print "warn%s is running" % func.__name__
                    return func(*args, **kwargs)
                return __deco
            return _deco
    
    
    @use_logging("warn")
    # @use_logging
    def bar():
        print('i am bar')
        print(bar.__name__)
    
    bar()

4. Class Decorator.

Using a class decorator can achieve the effect of a parameter decorator, but the implementation is more elegant and concise and can be extended flexibly through inheritance.

4.1 Class Decorator Example.

  1. Below is a python class decorator example.
    class logging(object):
        def __init__(self,level="warn"):
            self.level = level
    
        def __call__(self,func):
            @functools.wraps(func)
            def _deco(*args, **kwargs):
                if self.level == "warn":
                    self.notify(func)
                return func(*args, **kwargs)
            return _deco
    
        def notify(self,func):
            # Just log and do nothing else
            print "%s is running" % func.__name__
    
    
    @logging(level="warn")# invoke the __call__ function
    def bar(a,b):
        print('i am bar:%s'%(a+b))
    
    bar(1,3)

4.2 Inherit And Extend Class Decorator.

  1. The below example extends the above logging decorator.
    class email_loging(logging):
        '''
        An implementation of the above logging decorator that can send email to an administrator when a function is called
        '''
        def __init__(self, email='[email protected]', *args, **kwargs):
            self.email = email
            super(email_loging, self).__init__(*args, **kwargs)
    
        def notify(self,func):
            # Send an email to self.email
            print "%s is running" % func.__name__
            print "sending email to %s" %self.email
    
    
    @email_loging(level="warn")
    def bar(a,b):
        print('i am bar:%s'%(a+b))
    
    bar(1,3)

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.