1、装饰器是什么
装饰器本质是一个函数(函数内会再嵌入一个函数),或者是一个类(类中有__call__()方法),它可以在不对原函数做任何修改的前提下增加额外的功能,装饰器的返回是一个函数或者类对象。
2、为什么使用装饰器
有个函数f(),如果想计算函数f()的执行时间,我们需要定义一个新的函数deco(),它的参数是f(),然后给函数deco()嵌入计时功能。在实际使用中,每个业务函数都需要调用一下deco()函数。如果想要拓展一千万个函数功能,就要执行一千万次deco()函数,并修改deco()函数中的f(),所以这样并不理想。
def deco(func):
start_time = time.time()
f()
end_time = time.time()
execution_time = (end_time - start_time)*1000
print("time is %d ms" %execution_time)
def f():
print("hello")
if __name__ == '__main__':
deco(f)
3、装饰器的样子
3.1、函数装饰器
import time
def deco(f):
def wrapper():
start_time = time.time()
f()
end_time = time.time()
execution_time = (end_time - start_time)*1000
print("time is %d ms" %execution_time )
return wrapper
@deco
def f():
print("hello")
if __name__ == '__main__':
f()
- 这里的deco函数就是最原始的装饰器,它的参数是一个函数,返回值也是一个函数。
- @deco用来装饰f()函数,实际上相当于 f = deco(f),可以看出f有了新的引用,即函数deco以f作为参数得到的返回值就是f的新引用
- deco()函数的返回值为wrapper的引用,此时相当于f = wrapper,f指向了函数wrapper的引用,并且以f的初始值作为参数
- 主函数中调用了f(),也相当于调用了wrapper()方法,在执行wrapper()方法时,以f作为参数
- 将计算函数执行时间的函数作为一个装饰器,装饰到需要此功能的函数上,不会影响原功能,也不需要一直修改deco()函数
3.2、类装饰器
类装饰器主要依靠类的__call__方法,当使用@形式将装饰器附加到函数上时,就会调用此方法
可以让类的构造函数__init__()接受一个函数,然后重载__call__()并返回一个函数,也可以达到装饰器函数的效果。
class logging(object):
def __init__(self, f):
self.f = f
def __call__(self, *args, **kwargs):
print "[DEBUG]: enter function {func}()".format(
return self.f(*args, **kwargs)
@logging
def say(something):
print "say {}!".format(something)
4、带参数的装饰器
4.1、带固定参数的装饰器
如果使用装饰器的函数f()有参数,那么需要给装饰器也加上对应的参数,这个参数写在装饰器内层的函数上
def deco(f):
def wrapper(a,b):
start_time = time.time()
f(a,b)
end_time = time.time()
execution_time = (end_time - start_time)*1000
print("time is %d ms" % execution_time)
return wrapper
@deco
def f(a,b):
print("hello")
if __name__ == '__main__':
f(3,4)
4.2、无固定参数的装饰器
之所以无固定参数,原因是:可能有多个函数需要使用同一个装饰器,而每个函数的参数不一致,因此需要装饰器能够满足所有函数的参数形式
def deco(f):
def wrapper(*args, **kwargs):
start_time = time.time()
f(*args, **kwargs)
end_time = time.time()
execution_time_ = (end_time - start_time)*1000
print("time is %d ms" %execution_time)
return wrapper
@deco
def f(a,b):
print("be on")
time.sleep(1)
print("result is %d" %(a+b))
@deco
def f2(a,b,c):
print("be on")
time.sleep(1)
print("result is %d" %(a+b+c))
if __name__ == '__main__':
f2(3,4,5)
f(3,4)
PS:
- 在参数名之前使用一个星号def wrapper(*args),就是让函数接受任意多的位置参数,这些参数存储为一个元祖(tuple),显式声明的参数之外如果没有位置参数,这个参数就作为一个空元祖
- 在参数名之前使用两个星号def wrapper(**kwargs),就是让函数支持任意多的关键字参数,kwargs是一个正常的python字典类型,包含参数名和值,并且再调用是采取a=1,b=2,c=3的形式。如果没有更多的关键字参数,kwargs就是一个空字典。
5、使用多个装饰器,装饰一个函数
装饰器是可以叠加使用的,那么使用多个装饰器装饰同一个函数时,装饰器的调用顺序与使用 @ 语法糖声明的顺序相反。
下面的例子中:
f(3, 4) = deco01(deco02(f(3, 4)))
先使用离函数近的deco02(f)装饰,装饰后的结果被deco01(f)装饰。
def deco01(f):
def wrapper(*args, **kwargs):
print("this is deco01")
start_time = time.time()
f(*args, **kwargs)
end_time = time.time()
execution_time = (end_time - start_time)*1000
print("time is %d ms" % execution_time)
return wrapper
def deco02(f):
def wrapper(*args, **kwargs):
print("this is deco02")
f(*args, **kwargs)
return wrapper
@deco01
@deco02
def f(a,b):
print("be on")
time.sleep(1)
print("result is %d" %(a+b))
if __name__ == '__main__':
f(3,4)
6、Python内置装饰器
在Python中有三个内置的装饰器,都是跟class相关的:
staticmethod、classmethod 和property
- staticmethod 是类静态方法,其跟成员方法的区别是没有 self 参数,并且可以在类不进行实例化的情况下调用,即可以直接使用类调用,也可以用实例对象调用
- classmethod 与成员方法的区别在于所接收的第一个参数不是 self (类实例的指针),而是cls(当前类的具体类型),可以来调用类的属性,类的方法,实例化对象等。
- property 是属性的意思,表示可以通过通过类实例直接访问的信息
6.1、classmethod属性的使用原理:
class A(object):
bar = 1
def func1(self):
print ('foo')
@classmethod
def func2(cls):
print ('func2')
print (cls.bar) # 可以调用类的属性
cls().func1() # 可以使用cls调用func1()方法
A.func2() # 不需要实例化,可以直接调用func2()
输出结果为:
func2
1
foo
6.2、property属性的使用原理:
在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改:
score是一个属性变量
s = Student()
s.score = 9999
为了限制score的范围,可以通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成绩,但每次设置和获取都需要分别调用两个方法,因此可以使用Python内置的@property装饰器,它可以把一个方法变成属性调用,这个方法既能设置,又能获取
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
@property把一个getter方法变成属性,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作。
>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60),当作属性比变量使用了
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:
class Student(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value):
self._birth = value
@property
def age(self):
return 2015 - self._birth