python中使用’@’ 作为函数的修饰符,可以在模块或者类的定义层内对函数进行修饰,出现在函数定义的前一行,不允许和函数定义在同一行。一个修饰符就是一个函数,它将被修饰的函数作为参数,并返回修饰后的同名函数或其他可调用的东西。
装饰模式有很多经典的使用场景,例如插入日志、性能测试、事务处理等等,有了装饰器,就可以提取大量函数中与本身功能无关的类似代码,从而达到代码重用的目的。
1、通过装饰器重用功能对多对象进行性能测试:
我们可以直接把计时逻辑方法my_func内部,但是这样的话,如果要给另一个函数计时,就需要重复计时的逻辑。所以比较好的做法是把计时逻辑放到另一个函数中(timecount),如下:
# -*- coding: utf-8 -*-
"""
Created on Tue Jan 23 09:28:44 2018
python装饰器
@author: BruceWong
"""
import time
#设定计时器函数
def timecount(func):
timestart = time.time()
print(timestart)
func()
timeend = time.time()
print(timeend)
timecounts = (timeend - timestart)
print('func costs time %s'%timecounts)
#设定功能函数
#@timecount
def my_func():
timestart_ = time.time()
print('_____start_____',timestart_)
time.sleep(3)
timeend_ = time.time()
print('_____end_____',timeend_)
timecount(my_func)
my_func()
输出结果:
1516674631.0664797
_____start_____ 1516674631.0664797
_____end_____ 1516674634.066553
1516674634.066553
func costs time 3.0000734329223633
_____start_____ 1516674634.066553
_____end_____ 1516674637.069286
但是,上面的做法也有一个问题,就是所有的my_func调用处都要改为timecount(my_func)。下面,做一些改动,来避免计时功能对my_func函数调用代码的影响:
import time
#设定计时器函数
def timecount(func): #定义函数的变量只需写入函数的名称
def wrapper():
timestart = time.time()
print(timestart)
func() #在函数中调用函数一定要写完整的函数
timeend = time.time()
print(timeend)
timecounts = (timeend - timestart)
print('func costs time %s'%timecounts)
return wrapper
#设定功能函数
#@timecount
def my_func():
timestart_ = time.time()
print('_____start_____',timestart_)
time.sleep(3)
timeend_ = time.time()
print('_____end_____',timeend_)
print('my func name is %s'%my_func.__name__)
my_func = timecount(my_func) #此时得到的my_func只是一个wrapper函数,需要函数化运行得到结果
print('my func name is %s'%my_func.__name__)
my_func() #函数化运行得到结果
输出结果:
my func name is my_func
my func name is wrapper
1516674769.831145
_____start_____ 1516674769.831145
_____end_____ 1516674772.8370035
1516674772.8380172
func costs time 3.0068721771240234
经过了上面的改动后,一个比较完整的装饰器(timecount)就实现了,装饰器没有影响原来的函数,以及函数调用的代码。例子中值得注意的地方是,Python中一切都是对象,函数也是,所以代码中改变了”my_func”对应的函数对象。
2、装饰器语法糖,在Python中,可以使用”@”语法糖来精简装饰器的代码
- 初始计时使用@语法糖表达:
def timecount(func): #定义函数的变量只需写入函数的名称
timestart = time.time()
print(timestart)
func() #在函数中调用函数一定要写完整的函数
timeend = time.time()
print(timeend)
timecounts = (timeend - timestart)
print('func costs time %s'%timecounts)
#设定功能函数
@timecount
def my_func():
timestart_ = time.time()
print('_____start_____',timestart_)
time.sleep(3)
timeend_ = time.time()
print('_____end_____',timeend_)
输出结果:
1516675437.0982182
_____start_____ 1516675437.0982182
_____end_____ 1516675440.1111493
1516675440.1111493
func costs time 3.0129311084747314
- 加入wrapper后的表达:
import time
#设定计时器函数
def timecount(func): #定义函数的变量只需写入函数的名称
def wrapper():
timestart = time.time()
print(timestart)
func() #在函数中调用函数一定要写完整的函数
timeend = time.time()
print(timeend)
timecounts = (timeend - timestart)
print('func costs time %s'%timecounts)
return wrapper
#设定功能函数
#使用装饰器@timecount
@timecount
def my_func():
timestart_ = time.time()
print('_____start_____',timestart_)
time.sleep(3)
timeend_ = time.time()
print('_____end_____',timeend_)
print('my func name is %s'%my_func.__name__)
#my_func = timecount(my_func)
print('my func name is %s'%my_func.__name__)
my_func()
输出结果:
my func name is wrapper
1516675022.025378
_____start_____ 1516675022.025378
_____end_____ 1516675025.0261602
1516675025.0261602
func costs time 3.0007822513580322
使用了”@”语法糖后,我们就不需要额外代码来给”my_func”重新赋值了,其实@timecount的本质就是my_func = timecount(my_func),当认清了这一点后,后面看带参数的装饰器就简单了。
3、被装饰的函数带参数
对于被装饰函数需要支持参数的情况,我们只要使装饰器的内嵌函数支持同样的表达式即可。
def timecount(func): #定义函数的变量只需写入函数的名称
timestart = time.time()
print(timestart)
func(3,4) #在函数中调用函数一定要写完整的函数
timeend = time.time()
print(timeend)
timecounts = (timeend - timestart)
print('func costs time %s'%timecounts)
#设定功能函数
@timecount
def my_func(a,b):
timestart_ = time.time()
print('_____start_____',timestart_)
time.sleep(3)
print('result is %d'%(a*b))
timeend_ = time.time()
print('_____end_____',timeend_)
输出结果:
1516675735.2867255
_____start_____ 1516675735.2867255
result is 12
_____end_____ 1516675738.29806
1516675738.29806
func costs time 3.0113344192504883
这里还有一个问题,如果多个函数拥有不同的参数形式,怎么共用同样的装饰器?在Python中,函数可以支持(*args, **kwargs)可变参数,所以装饰器可以通过可变参数形式来实现内嵌函数的签名。
'''
注意在 #1 处函数 inner 接收任意数量和任意类型的参数,然后在 #2 处将他们传递给被包装的函数。这样一来我们可以包装或装饰任意函数,而不用管它的签名。
'''
def logger(func):
def inner(*args, **kwargs): #1
print("Arguments were: %s, %s" % (args, kwargs))
return func(*args, **kwargs) #2
return inner
@logger
def foo1(x, y=1):
return x * y
@logger
def foo2():
return 2
输出结果:
Arguments were: (5, 4), {}
Arguments were: (1,), {}
Arguments were: (), {}
20 1 2
4、带参数的装饰器
- 如果装饰器本身需要支持参数,那么装饰器就需要多一层的内嵌函数
# -*- coding: utf-8 -*-
"""
Created on Tue Jan 23 09:28:44 2018
python装饰器
@author: BruceWong
"""
import time
#设定计时器函数
def timecount(arg = True):
if arg:
def timecount(func): #定义函数的变量只需写入函数的名称
timestart = time.time()
print(timestart)
func(3,4) #在函数中调用函数一定要写完整的函数
timeend = time.time()
print(timeend)
timecounts = (timeend - timestart)
print('func_one costs time %s'%timecounts)
else:
def timecount(func):
timestart = time.time()
print(timestart)
func() #在函数中调用函数一定要写完整的函数
timeend = time.time()
print(timeend)
timecounts = (timeend - timestart)
print('func_two costs time %s'%timecounts)
return timecount
#设定功能函数
@timecount(True)
def my_func_one(a,b):
timestart_ = time.time()
print('_____start_____',timestart_)
time.sleep(3)
print('result is %d'%(a*b))
timeend_ = time.time()
print('_____end_____',timeend_)
@timecount(False)
def my_func_two():
timestart_ = time.time()
print('_____start_____',timestart_)
time.sleep(1)
timeend_ = time.time()
print('_____end_____',timeend_)
输出结果:
1516676322.4453025
_____start_____ 1516676322.4453025
result is 12
_____end_____ 1516676325.4502711
1516676325.4502711
func_one costs time 3.0049686431884766
1516676325.4502711
_____start_____ 1516676325.4502711
_____end_____ 1516676326.4581466
1516676326.4581466
func_two costs time 1.0078754425048828
5、装饰器调用顺序
- 装饰器是可以叠加使用的,那么这是就涉及到装饰器调用顺序了。对于Python中的”@”语法糖,装饰器的调用顺序与使用 @ 语法糖声明的顺序相反
#装饰器调用顺序
import time
def timecount(func):
timestart = time.time()
print(timestart)
func() #在函数中调用函数一定要写完整的函数
timeend = time.time()
print(timeend)
timecounts = (timeend - timestart)
print('func costs time %s'%timecounts)
def printf(func):
print('kneel')
@printf
@timecount
def my_func():
timestart_ = time.time()
print('_____start_____',timestart_)
time.sleep(3)
timeend_ = time.time()
print('_____end_____',timeend_)
输出结果:
1516677094.5291004
_____start_____ 1516677094.5291004
_____end_____ 1516677097.5339239
1516677097.5339239
func costs time 3.0048234462738037
kneel
Python内置装饰器
在Python中有三个内置的装饰器,都是跟class相关的:staticmethod、classmethod 和property。
- staticmethod 是类静态方法,其跟成员方法的区别是没有 self 参数,并且可以在类不进行实例化的情况下调用
class func():
tall = 180
hobbies = ['football','codemaker']
def __init__(self,name,age):
self.name = name
self.age = age
@staticmethod
def info(): #没有 self 参数,并且可以在类不进行实例化的情况下调用
print('I am %s and my hobbies are %s'%(func.tall,func.hobbies))
Bruce = func('Bruce',25)
Bruce.info()
输出结果:
I am 180 and my hobbies are ['football', 'codemaker']
- classmethod 与成员方法的区别在于所接收的第一个参数不是 self (类实例的指针),而是cls(当前类的具体类型)
- property 是属性的意思,表示可以通过通过类实例直接访问的信息