Python 装饰器用法
一、python装饰器工作原理
了解装饰器之前需要了解什么是闭包函数
必包函数,简而言之,就是函数的嵌套,一个函数嵌套一个函数.
所以Python的装饰器本质上是一个嵌套函数,它接受被装饰的函数(func)作为参数,并返回一个包装过的函数。这样我们可以在不改变被装饰函数的代码的情况下给被装饰函数或程序添加新的功能。有了装饰器,我们就可以抽离出大量与函数功能本身无关的代码,增加一个函数的重用性。
@语法糖
它放在一个函数开始定义的地方,它就像一顶帽子一样戴在这个函数的头上。和这个函数绑定在一起。在我们调用这个函数的时候,第一件事并不是执行这个函数,而是将这个函数作为参数传入它头顶上这顶帽子,这顶帽子我们称之为装饰器或装饰函数
图注解:如果在调用b()时,又想调用到a()的方法,就把a()作为一个装饰器,用@语法糖装饰到b()上,相当于b()作为参数传入到a()中实例出一个对象即b=a(b),可以理解@a = a(b)
装饰器调用顺序
执行单个装饰器内部调用顺序:
执行多个装饰器内部调用顺序:
使用装饰器遵循就近原则,由近到远;装饰器内内部调用遵循就远原则,由远到近
二、python有哪些装饰器及作用
1.函数装饰器
函数是一个对象,意味着:
能在函数中定义一个函数
能作为参数传递
能作为返回值
def cap(func):
# *args表示所有的位置参数,**kwargs表示所有的关键字参数
def wrapper(*args, **kwargs):
print('要带帽子')
return func(*args, **kwargs)
return wrapper
@cap
def person():
print('我是个人')
person()
这里在函数前加上 @cap 相当于在定义函数后执行了一条语句, cap_person = cap(person),再执行cap_person()
2.带参装饰器
foreign = "American"
def param(value):
def param_decorator(func):
# *args表示所有的位置参数,**kwargs表示所有的关键字参数
def wrapper(*args, **kwargs):
if value == "American":
print("我要带黄帽子")
elif value == "Chinese":
print("我要带红帽子")
return func(*args, **kwargs)
return wrapper
return param_decorator
@param(foreign)
def person_A():
print('我是个美国人')
@param("Chinese")
def person_B():
print('我是个中国人')
person_A()
person_B()
我们可以在装饰器外部再套上一层函数,用该函数的参数接收我们想要打印的数据,并将先前的 param_decorator 函数作为返回值。这就是之前学到的闭包的一种功能,就是用闭包来生成一个命名空间,在命名空间中保存我们要打印的值 value。
3.warps装饰器
一个函数不止有他的执行语句,还有着 __name__(函数名),__doc__ (说明文档)等属性,我们之前的例子会导致这些属性取的是装饰器的属性信息,而非执行的函数的属性信息。
一般通过 functools 模块下的 wraps 装饰器。
# -*- coding: UTF-8 -*-
import functools
from functools import wraps
import time
st = time.localtime().tm_sec
def wraps_decorator(func):
# *args表示所有的位置参数,**kwargs表示所有的关键字参数
# @wraps(func)
@functools.wraps(func) # 不使用内置的wraps时,实际的内存地址是指向了wrapper()函数
def wrapper(*args, **kwargs):
"""我是个帽子"""
print(st)
return func(*args, **kwargs)
return wrapper
@wraps_decorator
def Chinese():
""" 中国人带红帽子 """
@wraps_decorator
def American():
"""美国人带黄帽子"""
American()
print(American.__name__, American.__doc__)
Chinese()
print(Chinese.__name__, Chinese.__doc__)
4.内置装饰器
有三种我们经常会用到的装饰器, property、 staticmethod、 classmethod,主要用于类方法上
a.property 装饰器
property 装饰器用于类中的函数,使得我们可以像访问属性一样来获取一个函数的返回值。
@property装饰器就是负责把一个方法变成属性调用,快速实现set和get方法
class ClassRoom(object):
course = 'English'
student = 'Lily'
def __init__(self, score):
self.score = score
@property
def score(self):
return self._score
@score.setter # 由上面的装饰器衍生出来的装饰器,设置属性信息
def score(self, value):
self._score = value
print(self._score)
@score.getter # 获取属性信息
def score(self):
print('课程:' + self.course + '学生:' + self.student + '分数:' + self._score)
return self.course + self.student + self._score
s = ClassRoom("100")
s.score = "70" # 给属性赋值
s.score # 获取属性信息
代码中score()函数加上@property装饰器后就作为这个类的属性信息,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作.我们在对实例属性操作的时候,就知道该属性很可能不是直接暴露的,而是通过getter和setter方法来实现的。
b.staticmethod 装饰器
staticmethod 装饰器同样是用于类中的方法,这表示这个方法将会是一个静态方法,意味着该方法可以直接被调用无需实例化,但同样意味着它没有 self 参数,也无法访问实例化后的对象。
staticmethod 是类静态方法,其跟成员方法的区别是没有 self 参数,并且可以在类不进行实例化的情况下调用
class ClassRoom:
course = 'English'
teacher = 'Tom'
student = 'Lily'
@staticmethod # 加了@staticmethod,静态方法,可直接被调用无需实例
def sth():
print("hello 把外部方法集成到类里")
ClassRoom.sth()
c.classmethod 装饰器
classmethod 依旧是用于类中的方法,这表示这个方法将会是一个类方法,意味着该方法可以直接被调用无需实例化,但同样意味着它没有 self 参数,也无法访问实例化后的对象。相对于 staticmethod 的区别在于它会接收一个指向类本身的 cls 参数。
classmethod 与成员方法的区别在于所接收的第一个参数不是 self (类实例的指针),而是cls(当前类的具体类型)
class ClassRoom(object):
course = 'english'
teacher = 'lily'
student = 'tom'
@staticmethod
def get_info1(): # 加了@staticmethod,静态方法,可直接被调用无需实例
print("---我是staticmethod---")
def __init__(self, course, teacher, student):
self.course = course
self.teacher = teacher
self.student = student
@classmethod # 类方法,可直接被调用无需实例,会接收自身的cls参数,好处:以后重构类的时候无需修改构造函数,只需处理额外的函数然后用classmethod装饰即可
def get_info(cls):
print("---我是classmethod---")
print('课程:' + cls.course, '老师: ' + cls.teacher, '学生:' + cls.student)
def out_info(self): # 普通函数调用
print("course: " + self.course, "teacher:" + self.teacher, "student: " + self.student)
# 普通函数输出,需要先对类进行实例化才能调用
Info = ClassRoom('english', 'lily', 'tom')
Info.out_info()
# staticmethod静态类方法,无需实例可直接调用
ClassRoom.get_info1()
# classmethod类方法,无需实例可直接调用
ClassRoom.get_info()
5.类装饰器
定义:类装饰器就是通过类来装饰函数
类能实现装饰器的功能, 是由于当我们调用一个对象时,实际上调用的是它的 __call__ 方法。
通过这个特性,我们便可以用类的方式来完成装饰器,功能与刚开始用函数实现的一致。
类装饰器包括类装饰函数以及类装饰类、在执行调用装饰方面,类装饰器与函数装饰器是有区别的 类装饰器在整体定义完成之后就立即执行装饰, 函数装饰器在整体定义完成后必须显样调用去执行装饰
# -*- coding: UTF-8 -*-
def info():
print("test")
class Decorator:
print('----1')
def __init__(self, func):
print('----2')
self.func = func
def __call__(self, *args, **kwargs): # 把类的实例变成可调用对象(callable)
print('hi')
result = self.func(*args, **kwargs) # 类装饰器装饰函数功能
print('----3')
return result
@Decorator # @Decorator =》say_hello=Decorator(say_hello)
def say_hello():
print('同学你好')
info()
say_hello()
三、python装饰器适用场景
Python的装饰器广泛应用于缓存、权限校验(如django中的@login_required和@permission_required装饰器)、性能测试(比如统计一段程序的运行时间)和插入日志等应用场景.
以下例子为日志应用场景的典型例子,统计一段程序的运行日志结果
# -*- coding: UTF-8 -*-
import functools
import logging
from functools import wraps
import datetime
def _logger():
logger = logging.getLogger(__name__)
logger.setLevel('DEBUG')
handler = logging.StreamHandler()
formatter = logging.Formatter(
'[%(levelname)s][%(process)d][%(thread)d]--%(asctime)s--[%(filename)s %(funcName)s %(lineno)d]: %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
def running_logger(logger):
def log(func):
@functools.wraps(func)
def inner(*args, **kwargs):
start_time = datetime.datetime.now()
rs = func(*args, **kwargs)
exec_time = str(datetime.datetime.now() - start_time)
logger.info('[func: {}] cost: {}ms, results:{}.'.format(func.__name__, exec_time, rs))
return inner
return log
logger = _logger()
@running_logger(logger)
def select_sort(list):
'''doc of sort'''
for i in range(len(list) - 1):
min_index = i
for j in range(i + 1, len(list)):
if list[j] < list[min_index]:
min_index = j
list[min_index], list[i] = list[i], list[min_index]
return list
@running_logger(logger)
def bubble_sort(list):
for i in range(len(list) - 1):
for j in range(len(list) - 1 - i):
if list[j] > list[j + 1]:
list[j], list[j + 1] = list[j + 1], list[j]
return list
@running_logger(logger)
def shell_sort(list):
mid = len(list) // 2
while mid > 0:
for i in range(mid, len(list)):
j = i
cur = list[i]
while j - mid >= 0 and cur < list[j - mid]:
list[j] = list[j - mid]
j -= mid
list[j] = cur
mid //= 2
return list
select_sort([1, 51, 3, 8, 31, 36, 89, 30, 90, 45, 76, 32, 88, 90])
bubble_sort([10, 5, 32, 8, 31, 36, 89, 30, 34, 26, 66, 27, 98, 3])
shell_sort([10, 5, 32, 8, 31, 36, 89, 30, 34, 26, 66, 27, 98, 3])