装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
一、装饰器是什么?
装饰器,顾名思义,就是增强函数或类的功能的一个函数。
这么说可能有点绕。
举个例子:如何计算函数的执行时间?
如下,你需要计算 add 函数的执行时间。
# 函数 def add(a, b): res = a + b return res
你可能会这么写
import time def add(a, b) start_time = time.time() res = a + b exec_time = time.time() - start_time print("add函数,花费的时间是:{}".format(exec_time)) return res
这个时候,老板又让你计算减法函数(sub)的时间。不用装饰器的话,你又得重复写一段减法的代码。
def sub(a, b) start_time = time.time() res = a - b exec_time = time.time() - start_time print("sub函数,花费的时间是:{}".format(exec_time)) return res
这样显得很麻烦,也不灵活,万一计算时间的代码有改动,你得每个函数都要改动。
所以,我们需要引入装饰器。
使用装饰器之后的代码是这样的
import time # 定义装饰器 def time_calc(func): def wrapper(*args, **kargs): start_time = time.time() f = func(*args,**kargs) exec_time = time.time() - start_time return f return wrapper # 使用装饰器 @time_calc def add(a, b): return a + b @time_calc def sub(a, b): return a - b
是不是看起来清爽多了?
装饰器的作用:增强函数的功能,确切的说,可以装饰函数,也可以装饰类。
装饰器的原理:函数是python的一等公民,函数也是对象。
定义装饰器
def decorator(func): def wrapper(*args,**kargs): # 可以自定义传入的参数 print(func.__name__) # 返回传入的方法名参数的调用 return func(*args,**kargs) # 返回内层函数函数名 return wrapper
二、使用装饰器
假设decorator是定义好的装饰器。
方法一:不用语法糖@符号
# 装饰器不传入参数时 f = decorator(函数名) # 装饰器传入参数时 f = (decorator(参数))(函数名)
方法二:采用语法糖@符号
# 已定义的装饰器 @decorator def f(): pass # 执行被装饰过的函数 f()
装饰器可以传参,也可以不用传参。
自身不传入参数的装饰器(采用两层函数定义装饰器)
def login(func): def wrapper(*args,**kargs): print('函数名:%s'% func.__name__) return func(*args,**kargs) return wrapper @login def f(): print('inside decorator!') f() # 输出: # >> 函数名:f # >> 函数本身:inside decorator!
自身传入参数的装饰器(采用三层函数定义装饰器)
def login(text): def decorator(func): def wrapper(*args,**kargs): print('%s----%s'%(text, func.__name__)) return func(*args,**kargs) return wrapper return decorator # 等价于 ==> (login(text))(f) ==> 返回 wrapper @login('this is a parameter of decorator') def f(): print('2019-06-13') # 等价于 ==> (login(text))(f)() ==> 调用 wrapper() 并返回 f() f() # 输出: # => this is a parameter of decorator----f # => 2019-06-13
functools.wraps
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表,先看例子:
装饰器
def logged(func): def with_logging(*args, **kwargs): print(func.__name__ + " was called") return func(*args, **kwargs) return with_logging
函数
@logged def f(x): """does some math""" return x + x * x
该函数完成等价于:
def f(x): """does some math""" return x + x * x f = logged(f)
不难发现,函数f被with_logging取代了,当然它的docstring,__name__就是变成了with_logging函数的信息了。
print(f.__name__) # prints 'with_logging' print(f.__doc__) # prints None
这个问题就比较严重的,好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了。
from functools import wraps def logged(func): @wraps(func) def with_logging(*args, **kwargs): print(func.__name__ + " was called") return func(*args, **kwargs) return with_logging @logged def f(x): """does some math""" return x + x * x print(f.__name__) # prints 'f' print(f.__doc__) # prints 'does some math'
装饰器的顺序
@a @b @c def f ():
等效于
f = a(b(c(f)))
三、内置装饰器
常见的内置装饰器有三种,@property、@staticmethod、@classmethod
@property
把类内方法当成属性来使用,必须要有返回值,相当于getter;
假如没有定义 @func.setter 修饰方法的话,就是只读属性
class Car: def __init__(self, name, price): self._name = name self._price = price @property def car_name(self): return self._name # car_name可以读写的属性 @car_name.setter def car_name(self, value): self._name = value # car_price是只读属性 @property def car_price(self): return str(self._price) + '万' benz = Car('benz', 30) print(benz.car_name) # benz benz.car_name = "baojun" print(benz.car_name) # baojun print(benz.car_price) # 30万
@staticmethod
静态方法,不需要表示自身对象的self和自身类的cls参数,就跟使用函数一样。
@classmethod
类方法,不需要self参数,但第一个参数需要是表示自身类的cls参数。
例子
class Demo(object): text = "三种方法的比较" def instance_method(self): print("调用实例方法") @classmethod def class_method(cls): print("调用类方法") print("在类方法中 访问类属性 text: {}".format(cls.text)) print("在类方法中 调用实例方法 instance_method: {}".format(cls().instance_method())) @staticmethod def static_method(): print("调用静态方法") print("在静态方法中 访问类属性 text: {}".format(Demo.text)) print("在静态方法中 调用实例方法 instance_method: {}".format(Demo().instance_method())) if __name__ == "__main__": # 实例化对象 d = Demo() # 对象可以访问 实例方法、类方法、静态方法 # 通过对象访问text属性 print(d.text) # 通过对象调用实例方法 d.instance_method() # 通过对象调用类方法 d.class_method() # 通过对象调用静态方法 d.static_method() # 类可以访问类方法、静态方法 # 通过类访问text属性 print(Demo.text) # 通过类调用类方法 Demo.class_method() # 通过类调用静态方法 Demo.static_method()
@staticmethod 和 @classmethod 的 区别 和 使用场景:
在上述例子中,我们可以看出,
区别
在定义静态类方法和类方法时,@staticmethod 装饰的静态方法里面,想要访问类属性或调用实例方法,必须需要把类名写上;
而@classmethod装饰的类方法里面,会传一个cls参数,代表本类,这样就能够避免手写类名的硬编码。
在调用静态方法和类方法时,实际上写法都差不多,一般都是通过 类名.静态方法() 或 类名.类方法()。
也可以用实例化对象去调用静态方法和类方法,但为了和实例方法区分,最好还是用类去调用静态方法和类方法。
使用场景
所以,在定义类的时候,
假如不需要用到与类相关的属性或方法时,就用静态方法@staticmethod;
假如需要用到与类相关的属性或方法,然后又想表明这个方法是整个类通用的,而不是对象特异的,就可以使用类方法@classmethod。
浙公网安备 33010602011771号