Python decorator

Python decorator#

Introducing decorators##

What if you want to execute some other code before and after a function is executed, such as printing a log to output the calling status of this function?

#! /usr/bin/env python
# coding=utf-8

def logger(fn):									#Function as a parameter, fn can be any parameter
 def wrap(*args,**kwargs):					#Variable parameters args and kwargs
  print('call {}'.format(fn.__name__))	
  ret =fn(*args,**kwargs)				#Parameter deconstruction during function call
  print('{} called'.format(fn.__name__))return ret								#Return the return value of the function
 return wrap

def add(x, y):return x + y

logger_add =logger(add)print(logger_add.__name__)print(logger_add)
ret =logger_add(3,5)print(ret)

# Output result:
wrap
< function logger.<locals>.wrap at 0x7fba35f4fe18>
call add
add called
8

You can also use the following ways to achieve this effect

@ logger                                                                                  
def add(x, y):return x + y                                                                         ret =add(3,5)print(ret) 

# Output result:
call add
add called
8

This is a simple use of the Python decorator

What is a decorator?

Decorator is the name used for software design patterns. Decorators can dynamically change the functions of functions, methods or classes without directly using subclasses or changing the source code of the decorated function. Python decorator is a special change to Python syntax, which allows us to modify functions, methods and classes more conveniently.

When we write code in the following way:

@ logger
def add(x, y):...

It is the same as performing the following steps individually:

def add(x, y):...
logger_add =logger(add)

The code inside the decorator generally creates a new function, using *args and **kwargs to accept arbitrary parameters. The wrap() function in the above code is just like this. Inside this function, we need to call the original input function (the wrapped function, which is the input parameter of the decorator) and return its result. But you can also add any code you want to add, such as outputting the function call in the above code, you can also add timing processing and so on. This newly created wrap function will be returned as the result of the decorator, replacing the original function.

So in Python, the parameter of the decorator is a function, and the return value is a function of a function.

Examples of decorators: timing processing##

Write a decorator to calculate the execution time of a function

import time

def timethis(fn):
 def wrap(*args,**kwargs):
  start = time.time()
  ret =fn(*args,**kwargs)
  end = time.time()print(fn.__name__, end - start)return ret
 return wrap

If you want to time the add function:

@ timethis
def add(x, y):return x + y

ret =add(3,5)print(ret)

# Output result
add 1.9073486328125e-068

If you want to time the sleep function:

@ timethis
def sleep(x):
 time.sleep(x)sleep(3)

# Output result
sleep 3.003262519836426

Save the meta information of the decorated function##

What is the meta information of a function###

For example, the name of the decorator, the doc of the decorator, and so on. We can use the dir function to list all the meta-information of the function: dir(sleep), the output is as follows

['__ annotations__','__call__','__class__','__closure__','__code__','__defaults__','__delattr__','__dict__','__dir__','__doc__','__eq__','__format__','__ge__','__get__','__getattribute__','__globals__','__gt__','__hash__','__init__','__kwdefaults__','__le__','__lt__','__module__','__name__','__ne__','__new__','__qualname__','__reduce__','__reduce_ex__','__repr__','__setattr__','__sizeof__','__str__','__subclasshook__']

You can see that there are a lot of meta-information. The two attributes we use are __name__ and __doc__

And the __doc__ attribute is the document information of the function, which can be viewed through the help function

Why save the meta information of the decorated function###

Rewrite the application of the decorator 1: the sleep function in the timing process is as follows:

@ timeit
def sleep(x):'''This function is sleep.'''
 time.sleep(x)sleep(3)print(sleep.__name__)print(sleep.__doc__)

The output of the above code is as follows:

3.0032713413238525
wrap
None

It can be found that the __name__ of the sleep function is wrap, not sleep, and the __doc__ attribute is empty, not the docstring of the sleep function. In other words, the meta-information of the function decorated by the decorator has changed. At this time, if the program needs the meta-information of the function, then there is a problem.

How to save the meta information of the decorated function###

Solution 1: Manually assign value to the meta information of the decorated function

Take the two attributes of __name__ and __doc__ as examples

import time

def timeit(fn):
 def wrap(*args,**kwargs):
  start = time.time()
  ret =fn(*args,**kwargs)
  end = time.time()print(end - start)return ret
 wrap.__doc__ = fn.__doc__	#Manual assignment__doc__information
 wrap.__name__ = fn.__name__	#Manual assignment__name__information
 return wrap

@ timeit
def sleep(x):'''This function is sleep.'''
 time.sleep(x)if __name__ =="__main__":sleep(3)
 # print(dir(sleep))print(sleep.__name__)print(sleep.__doc__)

The output is as follows

3.004547119140625
sleep
This function is sleep.

It can be found that the two attributes of __name__ and __doc__ are indeed successfully assigned.

We can rewrite the process of meta-information assignment as a function, as follows

import time

def copy_properties(src, dst):	#Change the process of meta information assignment to function copy_properties
 dst.__name__ = src.__name__
 dst.__doc__ = src.__doc__

def timeit(fn):
 def wrap(*args,**kwargs):
  start = time.time()
  ret =fn(*args,**kwargs)
  end = time.time()print(end - start)return ret
 copy_properties(fn, wrap)	#Call copy_properties function to modify meta information
 return wrap

@ timeit
def sleep(x):'''This function is sleep.'''
 time.sleep(x)if __name__ =="__main__":sleep(3)
 # print(dir(sleep))print(sleep.__name__)print(sleep.__doc__)

After this modification, the problem can also be solved.

Continue to modify the copy_properties function so that copy_properties can return a function

def copy_properties(src):
 def _copy(dst):	#Built-in one_The copy function is easy to return
  dst.__name__ = src.__name__
  dst.__doc__ = src.__doc__
 return _copy

def timeit(fn):
 def wrap(*args,**kwargs):
  start = time.time()
  ret =fn(*args,**kwargs)
  end = time.time()print(end - start)return ret
 copy_properties(fn)(wrap)	#Call copy_properties function
 return wrap

The same can be a problem.

If you continue to modify the copy_properties function so that the _copy function is a decorator, pass in dst and return dst, and modify it as follows:

def copy_properties(src):	#Fix dst first, and pass in src
 def _copy(dst):	#Incoming dst
  dst.__name__ = src.__name__
  dst.__doc__ = src.__doc__
  return dst	#Return dst
 return _copy	#Returns a decorator

def timeit(fn):
 @ copy_properties(fn)	#How to use parameter decorator
 def wrap(*args,**kwargs):
  start = time.time()
  ret =fn(*args,**kwargs)
  end = time.time()print(end - start)return ret
 return wrap

copy_properties returns a decorator with parameters here, so you can decorate the wrap function directly according to the decorator's usage method. This process of modifying the copy_properties function is called function currying.

Option 2: Use the @wraps decorator of the functools library####

The @wraps decorator of the functools library is essentially an advanced version of the copy_properties function: it contains more function meta information. First check the help information of the wrap decorator:

import functools
help(functools.wraps)

The prototype of the wrap decorator function is:

wraps(wrapped, assigned=('module','name','qualname','doc','annotations'), updated=('dict',))

So this decorator will copy module and other meta information, but not all meta information, and will update the dict.

Examples of usage are as follows:

import time
import functools

def timeit(fn):
 @ functools.wraps(fn)	#Use of wraps decorator
 def wrap(*args,**kwargs):
  start = time.time()
  ret =fn(*args,**kwargs)
  end = time.time()print(end - start)return ret
 return wrap

def sleep(x):
 time.sleep(x)print(sleep.__name__)print(sleep.__doc__)

Write a decorator with parameters##

If the above timeit decorator, we need to output the name and execution time of the function whose execution time exceeds a few seconds (such as one second), then we need to pass in a parameter s to the decorator, which represents the incoming time interval, the default is 1s .

We can wrap a function timeitS outside the written decorator. The time interval s is passed in as a parameter of this function and is visible to the inner function, and then this function returns the written decorator.

import time
import functools

def timeitS(s):
 def timeit(fn):
  @ functools.wraps(fn)
  def wrap(*args,**kwargs):
   start = time.time()
   ret =fn(*args,**kwargs)
   end = time.time()if end - start > s:print('call {} takes {}s'.format(fn.__name__, end - start))else:print('call {} takes {}s less than {}'.format(fn.__name__, end - start, s))return ret
  return wrap
 return timeit

@ timeitS(2)
def sleep(x):
 time.sleep(x)sleep(3)sleep(1)

The output is as follows:

call sleep takes 3.001342535018921s
call sleep takes 1.000471830368042s less than 2

Therefore, we can understand the decorator with parameters as:

Recommended Posts

Python decorator
Python multithreading
Python CookBook
Python FAQ
Python3 dictionary
python (you-get)
Python string
Python basics
Python descriptor
Python basics 2
Python exec
Python notes
Python3 tuple
CentOS + Python3.6+
Python advanced (1)
Python IO
Python multithreading
Python toolchain
Python3 list
Python multitasking-coroutine
Python overview
python introduction
Python analytic
Python basics
07. Python3 functions
Python basics 3
Python multitasking-threads
Python decorator simple usage example summary
Python functions
python sys.stdout
python operator
Python entry-3
Centos 7.5 python3.6
Python string
python queue Queue
Python basics 4
Python basics 5
Python class decorator, use a small demo
Centos6 install Python2.7.13
Python answers questions
Python basic syntax (1)
Python exit loop
Ubuntu16 upgrade Python3
Centos7 install Python 3.6.
ubuntu18.04 install python2
Python classic algorithm
Relearn ubuntu --python3
Python2.7 [Installation Tutorial]
Python string manipulation
Python 3.9 is here!
Python study notes (1)
CentOS7 upgrade python3
Python3 basic syntax
Python review one
linux+ubuntu solve python
Functions in python
Python learning-variable types
CentOS install Python 3.6
Python file operation
ubuntu12.04 install python3
Python design patterns