什么是装饰器?
简单来说,可以把装饰器理解为一个包装函数
的函数,它一般将传入的函数或者是类
做一定的处理,返回修改之后的对象。
所以我们能够在不修改原函数
的基础上,在执行原函数前去执行别的代码
,比较常用的场景有日志插入,事务处理
等
应用场景
常用于缓存、权限校验、日志记录、性能测试、事务处理等场景
例子
我们知道,在python中函数也是被视为对象的,可以作为参数传递,那么假如把计算机耗时操作 独立为一个单独的函数,然后只要往里头传入需要计算耗时的函数 就可以不用在函数中额外添加计算耗时的语句啦
def calc_spend_time(func,*args,**kargs):
start_time = datetime.datetime.now()
result = fun(*args,**kargs)
end_time = datetime.datetime.now()
print "result:",result,"used:",(end_time - start_time).microseconds
def calc_add(a,b):
return a + b
calc_spend_time(calc_add,1,1)
上述没用到装饰器看起来也不错,负责计算的函数不用更改代码,只需调用的时作为参数传给计算时间差的函数。
但就是这,调用的时候形式变了,不再是clac(1,2),而是calc_spend_time(clac_add,1,2)
,万一calc_add大规模被调用,那么还得一处一处找,然后修改过来,还是很麻烦???
那么可以在calc_spend_time()里把传入的calc_add()包装一下,然后返回包装后的新的函数,再将其赋值给clac_add,那么calc_add()的效果就和calc_spend_time(calc(),*args,*kwargs)的效果一样
import datetime
from functools import wraps
def calc_spend_time(func):
@wraps(func) # 把new_func的属性值赋给new_func,例如调用func.__name__ 是显示的是func,还是new_fun
def new_func(a, b):
print(new_func.__name__)
start_time = datetime.datetime.now()
result = func(a, b)
end_time = datetime.datetime.now()
print("result:", result, "used:", (end_time - start_time).microseconds)
return new_func
def calc_add(a, b):
return a + b
calc_add = calc_spend_time(calc_add)
calc_add(1, 2)
语法糖:
上面的列子就是装饰器的概念,包含函数的函数<函数内部定义一个函数,这个函数需要调用某一个函数>
,上面的例子还可以更简洁
import datetime
def calc_spend_time(func):
def new_func(a,b):
start_time = datetime.datetime.now()
result = func(a,b)
end_time = datetime.datetime.now()
print "result",result,"used:",(end_time - start_time).microseconds,"us"
return new_func
@calc_spend_time
def calc_add(a,b):
return a+b
calc_add(1,2)
分析
@calc_spend_time就是语法糖,它的本质就是:calc_add = calc_spend_time(calc_add)
无参数的函数装饰器
import datetime
def calc_spend_time(func):
def new_func(*args,**kargs):
start_time = datetime.datetime.now()
result = func(*args,**kargs)
end_time = datetime.datetime.now()
print "result:",result,"used:",(end_time - start_time).microseconds,"us"
result new_func
@calc_spend_time
def calc_add(a,b):
return a+b
@calc_spend_time
def calc_diff(a,b):
return a - b
calc_add(a=1,b=2)
calc_diff(1,2)
分析
*args:把所有的参数按出现的顺序打包成tuple
**kargs:把所有的key=value形式的参数打包成一个dict
带参数的函数装饰器_1
假如我们需要知道函数的一些额外的信息,假如函数作者,可以通过给装饰器函数增加参数来实现
import datetime
def calc_spend_time(author):
def first_deco(func):
def new_func(*args, **kargs):
start_time = datetime.datetime.now()
result = func(*args, **kargs)
end_time = datetime.datetime.now()
print(author, "result:", result, "used:", (end_time - start_time).microseconds, "us")
return new_func
return first_deco
@calc_spend_time('author_1') # 如何给装饰器传入参数
def calc_add(a, b):
return a + b
@calc_spend_time('author_2')
def calc_diff(a, b):
return a - b
# 0. calc_spend_time('author_1')
# 1. calc_add=first_func(calc_add)
# 2. cal_add(1,2) =>> new_func(1,2)
calc_add(a=1, b=2)
calc_diff(1, 2)
代码分析
- 装饰器函数(calc_spend__time)的参数是author,那么此处肯定不能带参数了。
- 如何解决装饰器带参问题呢?还记得前面讲到的装饰器是由高阶函数+函数嵌套+闭包组成的吗?此处就要用到闭包的概念
带参数的函数装饰器_2
import time
from functools import wraps
DEFAULT_FMT = '[{elapsed:.8f}s] {name}({all_args_str}) -> {result}'
# 实现自定义日志输出格式
def clock(fmt=DEFAULT_FMT):
def decorate(func):
@wraps(func)
def wrapper(*args, **kwargs):
t0 = time.perf_counter()
result = func(*args, **kwargs) # 无法捕捉默认参数
elapsed = time.perf_counter() - t0
name = wrapper.__name__
print(name)
args_str = ', '.join(repr(arg) for arg in args) # 读取传入的元组参数
kwargs_str = ', '.join(f'{k}={w}' for k, w in sorted(kwargs.items())) # 读取传入的字典参数
all_args_str = ', '.join(astr for astr in [args_str, kwargs_str] if astr) # 去掉元组参数和字典参数为空的值
print(fmt.format(**locals()))
return result
return wrapper
return decorate
@clock()
def factorial_default_fmt(n: int) -> int:
return 1 if n < 2 else n * factorial_default_fmt(n - 1)
factorial_default_fmt(4)
python内置装饰器
python内置的装饰器有三个:
staticmethod
classmethod
property
staticmethod
把类中的方法定义为静态方法,使用staticmethod装饰的方法可以使用类或者类的实例对象来调用,不需要传入self
class Human(object):
"docstring for Human"
def __int__(self):
super(Human,self).__int__()
@staticmethod
def say(message):
if not message:
message = 'hello'
print 'I say %s'% message
def speak(self,message):
self.say(message)
Human.say(None)
human = Human()
human.speak('hi')
输出:
I say hello
I say hi
classmmethod
把类中的方法定义为类的方法,
使用classmethod装饰的方法可以使用类的或者类的实例对象来调用,
并将该class对象隐式的作为第一个参数传入
class Human(object):
"""docstring for Human"""
def __int__(self):
super(Human,self).__init__()
self.message = '111'
def say(message):
if not message:
message = "hlleo"
print "I say %s" %message
@classmethod
def speak(cls,message):
if not message:
message = 'hello'
cls.say(message)
human = Human()
human.speak('hi')
输出:
I say hello
I say hi
property
把方法变成属性
class Human(object):
def __init__(self,value):
super(Human,self).__init__()
self.arg = value
@property
def age(self):
return self._age
human = Human(20)
print human.age
检查传入的数据是否是空
def check_empty(func):
@wraps(func)
def wrapper(data_str):
if not data_str:
return ''
result = func(data_str)
return result
return wrapper
巨人的肩膀
小编强力推荐----作为启蒙知识:
这是我见过最全面的Python装饰器详解!没有学不会这种说法