Platitude Python advanced decorator

  • 2020-06-01 10:09:10
  • OfStack

Functions are objects

To understand the Python decorator, it is important to understand that in Python, a function is also an object, so you can think of the function name when you define the function as a reference to the function object. Since it is a reference, you can assign a function to a variable, or you can pass or return the function as a parameter. At the same time, functions can be redefined in the body.

Nature of decorator

You can restore what the decorator wants to do by writing an example of a pure function.


def decorator(func):
  
  def wrap():
    print("Doing someting before executing func()")
    func()
    print("Doing someting after executing func()")

  return wrap


def fun_test():
  print("func")


fun_test = decorator(fun_test)
fun_test()

# Output:
# Doing someting before executing func()
# func
# Doing someting after executing func()

A reference to the function pointed to by fun_test is passed to the decorator() function

The decorator() function defines the wrap() subfunction, which calls the fun_test() function passed in by the func reference, and does something else before and after calling the function

The decorator() function returns a reference to the internally defined wrap() function

fun_test receives the function reference returned by decorator(), thus pointing to a new function object

The front and back decoration of the fun_test() function is completed by calling the new function fun_test() to perform the functions of the wrap() function

Decorators are used in Python

The decorator feature can be easily used in Python via the @ symbol.


def decorator(func):
  
  def wrap():
    print("Doing someting before executing func()")
    func()
    print("Doing someting after executing func()")

  return wrap

@decorator
def fun_test():
  print("func")


fun_test()

# Output:
# Doing someting before executing func()
# func
# Doing someting after executing func()

The decorator function is already implemented, but at this point:


print(fun_test.__name__)

# Output:
# wrap

S 52en_test. s 54en__ has become s 55en, this is because s 56en () function has already rewritten the name and annotation document of our function. This problem can be solved by functools.wraps at this point. wraps takes a function to decorate and adds the ability to copy function names, comment documents, parameter lists, and so on. This allows us to access the properties of the function before the decoration in the decorator.

A more formal way of writing:


from functools import wraps

def decorator(func):
  @wraps(func)
  def wrap():
    print("Doing someting before executing func()")
    func()
    print("Doing someting after executing func()")

  return wrap


@decorator
def fun_test():
  print("func")


fun_test()
print(fun_test.__name__)

# Output:
# Doing someting before executing func()
# func
# Doing someting after executing func()
# fun_test

Parameter decorator

By returning a wrapper function, you can simulate the wraps decorator and construct a decorator with parameters.


from functools import wraps

def loginfo(info='info1'):
  def loginfo_decorator(func):
    @wraps(func)
    def wrap_func(*args, **kwargs):
      print(func.__name__ + ' was called')
      print('info: %s' % info)
      
      return func(*args, **kwargs)
    return wrap_func
  return loginfo_decorator
  
@loginfo()
def func1():
  pass
  
func1()

# Output:
# func1 was called
# info: info1

@loginfo(info='info2')
def func2():
  pass

func2()
# Output:
# func2 was called
# info: info2

A decorator classes

You can also implement decorators by writing classes, and make them available for inheritance and other object-oriented features that are more practical

First, write a decorator base class:


from functools import wraps

class loginfo:
  def __init__(self, info='info1'):
    self.info = info
    
  def __call__(self, func):
    @wrap
    def wrap_func(*args, **kwargs):
      print(func.__name__ + ' was called')
      print('info: %s' % self.info)
      
      self.after()  #  call after Method, which can be implemented in subclasses 
      return func(*args, **kwargs)
    return wrap_func

  def after(self):
    pass


@loginfo(info='info2')
def func1():
  pass
  
# Output:
# func1 was called
# info: info1

Extend the functionality of the decorator by inheriting the loginfo class:


class loginfo_after(loginfo):
  def __init__(self, info2='info2', *args, **kwargs):
    self.info2 = info2
    super(loginfo_after, self).__init__(*args, **kwargs)

  def after(self):
    print('after: %s' % self.info2)


@loginfo_after()
def func2():
  pass

func2()
  
# Output:
# func2 was called
# info: info1
# after: info2

Related articles: