Pthon Decorator Advancement

Posted by bdeonline on Wed, 08 May 2019 21:45:02 +0200

Catalog

Decorator Advancement

As you already know from the previous article, if you still don't understand what decoration is, please go back. Introduction to Decorator . The first one is an introduction to decoration. Decorator has a very important application in python language. From script development to web framework development with many functions. As we know, the decorated functions we have seen before are the simplest function format. The simplicity here refers to the form of a function without parameters, or that the function does not return (directly execute the result) values. The decorated functions in the actual development are not so simple format as we can see. More complex functions such as multiple return values, multiple parameters (formal parameters, keyword parameters), so this article is a step forward of python decorator.

1. Decorated functions have multiple parameters.

Scenario 1: A Simple Authentication Decorator

  • Example code

    from functools import wraps
    
    def auth2(func):
        @wraps(func)
        def wrapper(*arg, **kwargs):
            user = input("pls input password:").strip()
            if user == "faily":
                print("---welcome login----")
                func(*arg, **kwargs)
          else:
              print("----wrong password----")
      return wrapper
    
    @auth2
    def task2(name, is_happy="Yes"):
                    print("{0} is doing somthing, Is he happy? [{1}]".format(name, is_happy))
    
    
    if __name__ =="__main__":
         '''Decorator with parameters'''
         task2("faily")

    As you can see, the decorated function task2 has two parameters, name, is_happy, so how to pass the two parameters into the decorator function?

  1. According to the previous knowledge, we can see that when the decorator is executed, it will first pass the decorated function name task2 to the decorator function auth2. Then the parameters of the decorated function have to be passed in somewhere else. The only place that can accept these variables is the wrapper function.
  2. After the parameters are passed in through wrapper function, the parameters can be transferred to a decorated function.

2. Decorated functions have return values

  • Obviously, this function is the most common one. It shows the result immediately after execution, but passes it as a value.

    from functools import wraps
    
    def auth3(func):
    
        @wraps(func)
        def wrapper(arg):
            user = input("pls input password:").strip()
            if user == "faily":
                print("---welcome login----")
                return func(arg)
            else:
                print("----wrong password---")
    
        return wrapper
    
    @auth3
    def task3(name):
        print("do somthing ")
        return name
    
    if __name__ == "__main__":
        ts = task3("momo")
        print(ts)

    It's easy to understand that if a function has a return value, the decorated function will be returned by using the keyword return directly after the new function is executed in the wrapper function.

3. Embedding decorators in functions

Here we can understand as follows: since the decorator is a high-order function + nested function = decorator, can we have this combination, that is, the form of nested function + decorator function?

The answer is yes.

In fact, this application can also be understood as: before we wrote all the decorator functions without parameters, since the essence of the decorator is also a function, then can the decorator function with parameters? What's the format?
This is illustrated by the following examples

  • Go back to the log example and create a wrapper function that lets us specify a log file for output

    from functools import wraps
    
    def logit(logfile='out.log'):
    
        # Decorator 
        def logging_decorator(func):
            @wraps(func)
            def wrapped_function(*args, **kwargs):
                log_string = func.__name__ + " was called"
                print(log_string)
                # Open logfile and write content
                with open(logfile, 'a') as f:
                    # Now type the log to the specified logfile
                    f.write(log_string + '\n')
                return func(*args, **kwargs)
           return wrapped_function
        return logging_decorator
    
    #Default Decorator Parameters
    @logit()
    def myfunc1(action="run"):
        if action == "run":
            return True
        else:
            return False
    
    #Decorator Reference
    @logit(logfile='func2.log')
    def myfunc2(action = "stop"):
        if action == "run":
            return True
        else:
            return False
    
    if __name__ == "__main__":
      myfunc1()
      myfunc2()
    
    # Now a file called func2.log appears, which contains the string above. 

    In this example, do you think the decoration is too powerful?

  • By giving different parameters to the decorator, we can realize the new function of log output of different functions. We just add a layer of functions to the standard decorator. This function is actually used to transfer the parameters of the decorator into the package function.

4. Decorators

Scenario analysis: In operation and maintenance monitoring, we often need to record logs at different levels or generated by different apps, but when some parts of our application are still fragile, triggering exceptions may be something that needs more attention. For example, sometimes we just want to log to a file; sometimes you want to send an email when an exception occurs, and keep a log at the same time. This is a scenario using inheritance, but so far we have only seen the functions used to build the decorator.

Fortunately, classes can also be used to build decorators. So now we're rebuilding logit in the form of a class rather than a function.

  • Decorator class

    rom functools import wraps
    
    class logit(object):
        '''Decorator class'''
        def __init__(self, logfile='out.log'):
            self.logfile = logfile
    
        def __call__(self, func): # _ call_ indicates that this is a callable
            @wraps(func)
            def wrapped_function(*args, **kwargs):
                log_string = func.__name__ + " was called"
                print(log_string)
                # Open logfile and write
                with open(self.logfile, 'a') as f:
                    # Now type the log to the specified file
                    f.write(log_string + '\n')
                # Now, send a notification
                self.notify()
                return func(*args, **kwargs)
            return wrapped_function
    
        def notify(self):
            # logit logs, nothing else.
            pass

    An additional advantage of this implementation is that it is cleaner than nested functions, and that wrapping a function uses the same syntax as before:

    @logit()
    def myfunc1():
        pass

    Now let's create subclasses for logit to add email functionality.

    class email_logit(logit):
        '''
        //An implementation version of logit that can send an email to the administrator when a function is called
        '''
        def __init__(self, email='admin@myproject.com', *args, **kwargs):
            self.email = email
            super(email_logit, self).__init__(*args, **kwargs) # Integrated parent class
    
        def notify(self):
            # Send an email to self.email
            # It's not done here.
            pass

    From now on, @email_logit will have the same effect as @logit, but on the basis of the output log, an additional email will be sent to the administrator.

summary

Decoration Advancement has been finished, the next article we continue to study, multi-decoration

Topics: Python