理解装饰器的前提:1.所有东西都是对象(函数可以当做对象传递) 2.闭包
闭包的概念:
1)函数嵌套
2)内部函数使用外部函数的变量
3)外部函数的返回值为内部函数
应用
在用例执行时,可以用于监控各种异常
下面写一个最为简单的闭包的例子:
1 def test(name):
2 def test_in():
3 print(name)
4 return test_in
5
6 func = test('whyz')
7 func()
装饰器的原型:
1 import time
2 def showtime(func):
3 def wrapper():
4 start_time = time.time()
5 func()
6 end_time = time.time()
7 print('spend is {}'.format(end_time - start_time))
8
9 return wrapper
10
11 def foo():
12 print('foo..')
13 time.sleep(3)
14
15 foo = showtime(foo)----函数做为参数被调用
16 foo()
不带参数的装饰器:(装饰器,被装饰函数都不带参数)
1 import time
2 def showtime(func):
3 def wrapper():
4 start_time = time.time()
5 func()
6 end_time = time.time()
7 print('spend is {}'.format(end_time - start_time))
8
9 return wrapper
10
11 @showtime #foo = showtime(foo) 原理:代码运行到foo()后立即作为参数被函数showtime调用
12 def foo():
13 print('foo..')
14 time.sleep(3)
15
16 @showtime #doo = showtime(doo)
17 def doo():
18 print('doo..')
19 time.sleep(2)
20
21 foo()
22 doo()
带参数的被装饰的函数
1 import time
2 def showtime(func):
3 def wrapper(a, b):
4 start_time = time.time()
5 func(a,b)
6 end_time = time.time()
7 print('spend is {}'.format(end_time - start_time))
8
9 return wrapper
10
11 @showtime #add = showtime(add)
12 def add(a, b):
13 print(a+b)
14 time.sleep(1)
15
16 @showtime #sub = showtime(sub)
17 def sub(a,b):
18 print(a-b)
19 time.sleep(1)
20
21 add(5,4)
22 sub(3,2)
带参数的装饰器(装饰函数),
实际是对原有装饰器的一个函数的封装,并返回一个装饰器(一个含有参数的闭包函数),
当使用@time_logger(3)调用的时候,Python能发现这一层封装,并将参数传递到装饰器的环境去
1 import time
2 def time_logger(flag = 0): # 对原有装饰器showtime的封装,并返回showtime装饰器
3 def showtime(func):
4 def wrapper(a, b):
5 start_time = time.time()
6 func(a,b)
7 end_time = time.time()
8 print('spend is {}'.format(end_time - start_time))
9
10 if flag:
11 print('将此操作保留至日志')
12
13 return wrapper
14
15 return showtime
16
17 @time_logger(2) #得到闭包函数showtime,add = showtime(add)
18 def add(a, b):
19 print(a+b)
20 time.sleep(1)
21
22 add(3,4)
无固定参数的装饰器
import time
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))
if __name__ == '__main__':
f(3,4)
多个装饰器,装饰一个函数
import time
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)
print("deco01 end here")
return wrapper
def deco02(f):
def wrapper(*args, **kwargs):
print("this is deco02")
f(*args, **kwargs)
print("deco02 end here")
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)
装饰器调用顺序
装饰器是可以叠加使用的,装饰器执行顺序:
对于Python中的”@”语法糖,装饰器的调用顺序与使用 @ 语法糖声明的顺序相反。即先执行deco02,再执行deco01
”f(3, 4) = deco01(deco02(f(3, 4)))”。
类装饰器:一般依靠类内部的__call__方法
1 import time
2 class Foo(object):
3 def __init__(self, func):
4 self._func = func
5
6 def __call__(self):
7 start_time = time.time()
8 self._func()
9 end_time = time.time()
10 print('spend is {}'.format(end_time - start_time))
11
12 @Foo #bar = Foo(bar)
13 def bar():
14 print('bar..')
15 time.sleep(2)
16
17 bar()
使用装饰器的缺点:
1.位置错误的代码->不要在装饰器之外添加逻辑功能
2.不能装饰@staticmethod 或者 @classmethod已经装饰过的方法
3.装饰器会对原函数的元信息进行更改,比如函数的docstring,__name__,参数列表:
下面对装饰器第三个缺点进行剖析,
1 import time
2 def showtime(func):
3 def wrapper():
4 start_time = time.time()
5 func()
6 end_time = time.time()
7 print('spend is {}'.format(end_time - start_time))
8
9 return wrapper
10
11 @showtime #foo = showtime(foo)
12 def foo():
13 print('foo..')
14 time.sleep(3)
15
16 def doo():
17 print('doo..')
18 time.sleep(2)
19
20 print(foo.__name__)
21 print(doo.__name__)
结果为:
wrapper
doo
由此可以看出,装饰器会对原函数的元信息进行更改,可以使用wraps,进行原函数信息的添加
注解:wraps本身也是一个装饰器,他能把函数的元信息拷贝到装饰器函数中使得装饰器函数与原函数有一样的元信息
以下是一个wraps的例子:
1 import time
2 from functools import wraps
3 def showtime(func):
4
5 @wraps(func)
6 def wrapper():
7 start_time = time.time()
8 func()
9 end_time = time.time()
10 print('spend is {}'.format(end_time - start_time))
11
12 return wrapper
13
14 @showtime #foo = showtime(foo)
15 def foo():
16 print('foo..')
17 time.sleep(3)
18
19 def doo():
20 print('doo..')
21 time.sleep(2)
22
23 print(foo.__name__)
24 print(doo.__name__)
结果为:
foo
doo
Python内置装饰器
在Python中有三个内置的装饰器,都是跟class相关的:staticmethod、classmethod 和property。
- staticmethod 是类静态方法,其跟成员方法的区别是没有 self 参数,并且可以在类不进行实例化的情况下调用
- classmethod 与成员方法的区别在于所接收的第一个参数不是 self (类实例的指针),而是cls(当前类的具体类型)
- property 是属性的意思,表示可以通过通过类实例直接访问的信息
对于staticmethod和classmethod这里就不介绍了,通过一个例子看看property。
常用的内置装饰器解析:
1.staticmethod: 类似实现了静态方法 注入以后,可以直接 : 类名.方法的形式调用方法。
静态方法(staticmethod)详解
通常情况下,在类中定义的所有函数(注意了,这里说的就是所有,跟self啥的没关系,self也只是一个再普通不过的参数而已)都是对象的绑定方法,对象在调用绑定方法时会自动将自己作为参数传递给方法的第一个参数。除此之外还有两种常见的方法:静态方法和类方法,二者是为类量身定制的,但是实例非要使用,也不会报错。
是一种普通函数,位于类定义的命名空间中,不会对任何实例类型进行操作,python为我们内置了函数staticmethod来把类中的函数定义成静态方法
class Foo:
def test(x,y,z): #类中的一个函数,千万不要懵逼,self和x啥的没有不同都是参数名
print(x,y,z)
t=staticmethod(test) #把test函数做成静态方法
基于之前所学装饰器的知识,@staticmethod 等同于spam=staticmethod(spam),于是
class Foo:
@staticmethod #装饰器
def test(x,y,z):
print(x,y,z)
使用演示
class Foo:
@staticmethod
def test(x,y,z):
print(x,y,z)
print(type(Foo.test))#类型本质就是函数
Foo.test(1,2,3) #调用函数应该有几个参数就传几个参数
f1=Foo()
f1.test(3,67,99) #实例也可以使用,但通常静态方法都是给类用的,实例在使用时丧失了自动传值的机制
输出结果:
<class 'function'>
1 2 3
3 67 99
应用场景:编写类时需要采用很多不同的方式来创建实例,而我们只有一个init函数,此时静态方法就派上用场了
class Date:
def __init__(self,year,month,day):
self.year=year
self.month=month
self.day=day
@staticmethod
def now(): #用Date.now()的形式去产生实例,该实例用的是当前时间
t=time.localtime() #获取结构化的时间格式
return Date(t.tm_year,t.tm_mon,t.tm_mday) #新建实例并且返回
@staticmethod
def tomorrow():#用Date.tomorrow()的形式去产生实例,该实例用的是明天的时间
t=time.localtime(time.time()+86400)
return Date(t.tm_year,t.tm_mon,t.tm_mday)
a=Date('1987',11,27) #自己定义时间
b=Date.now() #采用当前时间
c=Date.tomorrow() #采用明天的时间
print(a.year,a.month,a.day)
print(b.year,b.month,b.day)
print(c.year,c.month,c.day)
总结:staticmethod方法:1)定义方法时,无需写入默认参数(self)
class classname:
def method(a): # method方法的参数无须写入默认参数self
print('====')
2)调用时,可以直接利用 类.方法 的形式,无须对类进行实例化
例:调用上述method方法:classname.method(2)即可。
2.property:经过property装饰过的函数 不再是一个函数,而是一个property,类似实现get,set方法
1 @property
2 def width(self):
3 return self.__width
4
5 @width.setter
6 def width(self, newWidth):
7 self.__width = newWidth
3.classmethod: 与staticmethod很相似,貌似就只有这一点区别:
第一个参数需要是表示自身类的 cls 参数,
可以来调用类的属性,类的方法,实例化对象等。
本文参考:
1.https://www.cnblogs.com/whyaza/p/9505205.html
2.https://www.cnblogs.com/yuzhanhong/p/9180212.html
3.https://www.jianshu.com/p/4c8c9bae5b78