目录
之前了解过Python中的装饰器、生成器和迭代器的概念,并没有系统的深入了解,平时写代码基本也没有碰到过这样的场景需求。最近在看tensorflow2.0框架相关的框架的时候,又看到了装饰器,作用不是很熟悉,觉得有必要做一个系统的学习,然后记录下来,加深理解同时以备后续复习。
一、什么是装饰器
什么是装饰器呢?为了有个直观的理解,直接上代码:
import time
def deco(f):
def wrapper():
print('I am a decorator!,Founction run begin!')
sta = time.time()
f()
end = time.time()
print('Founction run time is:',(end-sta))
print('Founction run finished!')
return wrapper
@deco
def do_something():
r = 0
for i in range(1,100,2):
r += i
print('r is:',r)
if __name__ == '__main__':
do_something()
上面的代码中@deco就是一个简单的装饰器,使用了语法糖@。顾名思义,装饰器就是对函数器装饰作用的一种函数。它能够在不改变原来函数代码的前提下,给函数增加额外的功能,只要是带有@语法糖的函数就是装饰器了。以上的代码中,原来函数只具备计算奇数和的功能,现在使用了@deco,就给它额外的增加了统计函数运行时间的功能。
看看输出结果:
I am a decorator!,Founction run begin!
r is: 2500
Founction run time is: 6.198883056640625e-06
Founction run finished!
二、为何要使用装饰器
为何要使用装饰器呢?上面代码中的功能不是很简单的吗?我直接写在原来的函数里不同样可以达到效果吗?看代码不使用装饰器:
def do_something():
print('I am a decorator!,Founction run begin!')
sta = time.time()
r = 0
for i in range(1,100,2):
r += i
print('r is:',r)
end = time.time()
print('Founction run time is:', (end - sta))
print('Founction run finished!')
if __name__ == '__main__':
do_something()
同样的能实现上面的功能,且同样这个总体的代码行数还减少呢,比装饰器貌似要优越一点啊?!。那么装饰器的优越性怎么体现出来的呢?现在设想有这样一个场景:你一个项目里面有成百上前个函数,现在需要记录每个函数的运行时间和输出一些日志之类的。那么不用装饰器就得在原来的每个函数增加相应的代码,试想一下,是每个函数,这里的代码就增加量无疑是很大的。那这个时候装饰器的作用就体现出来了,写一个装饰器函数,然后给每个调用的函数,弄一个@帽子,在原来的代码中,函数调用方式也不用修改,是不是很方便和简洁呀?看代码:
import time
def deco(f):
def wrapper():
print('I am a decorator!,Founction run begin!')
sta = time.time()
f()
end = time.time()
print('Founction run time is:',(end-sta))
print('Founction run finished!')
return wrapper
def log(f):
def wrapper():
f()
print('输出日志!\n')
return wrapper
@log
@deco
def do_something1():
r = 0
for i in range(1,100,2):
r += i
print('r1 is:',r)
@log
@deco
def do_something2():
r = 0
for i in range(1,100,3):
r += i
print('r2 is:',r)
@log
@deco
def do_something3():
r = 0
for i in range(1,100,4):
r += i
print('r3 is:',r)
if __name__ == '__main__':
do_something1()
do_something2()
do_something3()
不使用装饰器的代码:
import time
def do_something1():
print('I am a decorator!,Founction run begin!')
sta = time.time()
r = 0
for i in range(1,100,2):
r += i
print('r1 is:',r)
end = time.time()
print('Founction run time is:', (end - sta))
print('Founction run finished!')
print('输出日志!\n')
def do_something2():
print('I am a decorator!,Founction run begin!')
sta = time.time()
r = 0
for i in range(1, 100, 3):
r += i
print('r2 is:', r)
end = time.time()
print('Founction run time is:', (end - sta))
print('Founction run finished!')
print('输出日志!\n')
def do_something3():
print('I am a decorator!,Founction run begin!')
sta = time.time()
r = 0
for i in range(1, 100, 2):
r += i
print('r3 is:', r)
end = time.time()
print('Founction run time is:', (end - sta))
print('Founction run finished!')
print('输出日志!\n')
if __name__ == '__main__':
do_something1()
do_something2()
do_something3()
可以看到使用装饰器的代码量确实要少一点,代码冗余也少很多,更加的简洁和清晰!当然也能增加代码的逼格!
三、装饰器的分类
装饰器可以由函数实现也可以由类实现,从这个角度我们可以把装饰器分为函数式装饰器和类装饰器;另外装饰器可以从是否带有参数来区分,区分为含参装饰器和不含参装饰器;还可以从Python是否内置的角度分为内置和非内置装饰器。下面来详细看看这些不同的装饰器。
1、固定参数的函数式装饰器
普通的无参函数装饰器,就是上面代码中提到的。那接下来就看有参的函数装饰器。有参数,就是函数在调用的时候参入参数,装饰器能接收到参数,那我们不给装饰器设置参数,会有什么样的效果呢?
import time
def deco(f):
def wrapper():
print('I am a decorator!,Founction run begin!')
sta = time.time()
f()
end = time.time()
print('Founction run time is:',(end-sta))
print('Founction run finished!')
return wrapper
@deco
def do_something(a,b):
print('a:',a)
print('b:',b)
if __name__ == '__main__':
do_something('王鸥泡泡袖','王凯猎狐')
直接报错,如下:
wrapper()中缺少参数,因此必须添加上。
import time
def deco(f):
def wrapper(a,b):
print('I am a decorator!,Founction run begin!')
sta = time.time()
f(a,b)
end = time.time()
print('Founction run time is:',(end-sta))
print('Founction run finished!')
return wrapper
@deco
def do_something(a,b):
print('a:',a)
print('b:',b)
if __name__ == '__main__':
do_something('王鸥泡泡袖','王凯猎狐')
运行成功!
2、非固定参数的函数式装饰器
现在有多个函数需要用到同一个装饰器,同时这里每个函数的参数个数不相同,类型也不一样。那这种情况是什么样的呢?这个时候就需要用到*args,**kwargs了。这样就能代表不定参数了,前者可以是列表元祖,后者是字典。直接上代码:
import time
def deco(f):
def wrapper(*args,**kwargs):
print('I am a decorator!,Founction run begin!')
sta = time.time()
f(*args,**kwargs)
end = time.time()
print('Founction run time is:',(end-sta))
print('Founction run finished!')
return wrapper
@deco
def do_something(a,b):
print('a:',a)
print('b:',b)
@deco
def do_somethin1(text):
text += ':IG被滔博吊打!阿水牛逼!FPX被LGD吊打!冠军队是怎么回事?'
print(text)
@deco
def do_something2(dic):
for k,v in dic.items():
print('%s:%s'%(k,v))
if __name__ == '__main__':
do_something('王鸥泡泡袖','王凯猎狐')
do_somethin1('英雄联盟大事件!')
do_something2({'name':'夕小瑶','sex':'女','about':'NLPer大牛!'})
结果:
I am a decorator!,Founction run begin!
a: 王鸥泡泡袖
b: 王凯猎狐
Founction run time is: 8.106231689453125e-06
Founction run finished!
I am a decorator!,Founction run begin!
英雄联盟大事件!:IG被滔博吊打!阿水牛逼!FPX被LGD吊打!冠军队是怎么回事?
Founction run time is: 2.86102294921875e-06
Founction run finished!
I am a decorator!,Founction run begin!
name:夕小瑶
sex:女
about:NLPer大牛!
Founction run time is: 9.059906005859375e-06
Founction run finished!
3、特殊的@deco(param)
还有一种特殊的参数形式,那就是在@deco中写参数的。形如:
def deco(param):
def decorator(f):
def wrapper(*args,**kwargs):
if param == 'warning':
print('I am a decorator!,%s founction run begin!warning!'%f.__name__)
return f(*args, **kwargs)
else:
return f(*args, **kwargs)
return wrapper
return decorator
@deco(param='warning')
def do_something(a,b):
print('a:',a)
print('b:',b)
@deco(param='errors')
def do_something1(a):
print('a:',a)
结果:
I am a decorator!,do_something founction run begin!warning!
a: 猎狐
b: 王鸥王凯CP
a: 明天免费去做核酸检测了,终于能白嫖一次公司了哈哈哈!
其实这种特殊参数的装饰器的应用场景不是特别能理解,这样的装饰器到底意义在哪里!
4、类式装饰器
顾名思义,类式装饰器就是实现形式是一个类,这个和函数式装饰器是不同的。这里的实现就必须用的__call__和__init__这两个函数,__call__用来实现装饰的逻辑,__init__则是用来接收外部调用函数的。具体看实例:
class Decorater(object):
def __init__(self,fun):
self.fun = fun
def __call__(self, *args, **kwargs):
print('function %s() is running!'%(self.fun.__name__))
for ele in args:
print(ele)
return self.fun( *args, **kwargs)
@Decorater
def do_something(brand,msg):
print('%s的显卡RTX 2080ti,%s'%(brand,msg))
if __name__ == '__main__':
brand = 'Nvidia'
msg = '价格好贵买不起!'
do_something(brand,msg)
结果如下:
function do_something() is running!
Nvidia
价格好贵买不起!
Nvidia的显卡RTX 2080ti,价格好贵买不起!
上面的类装饰器是没有带参数的,同样的类装饰器也是可以带参数的。具体的实现形式如下:
class logger(object):
def __init__(self, level='Error'):#接收参数
self.level = level
def __call__(self, func): # 接受函数
def wrapper(*args, **kwargs):
print("[{level}]: the function {func}() is running..."\
.format(level=self.level, func=func.__name__))
func(*args, **kwargs)
return wrapper #返回函数
@logger(level='INFO')
def say(something):
print("speak{}!".format(something))
if __name__ == '__main__':
say("小度小度,你好呀!")
5、Python内置装饰器
Python里面常用的内置装饰器是:@property,@staticmethod和@classmethod
这里就简简单单的说一下它们的用法,具体的优势和区别就不做说明了。
@property
添加在方法上,可以使得方法变成类的属性,直接想访问属性的方式来访问;可以看到下面代码中有无@property访问方式的不一样。
class NLP(object):
def __init__(self,model,task,tools):
self.model = model
self.task = task
self.tools = tools
def set_model(self):
return self.model
def set_task(self):
return self.task
@property
def set_tools(self):
return self.tools
if __name__ == '__main__':
nlp = NLP('bert','classification','pytorch and python')
print(nlp.set_model()) #没有@property,必须要调用set_model()方法来实现访问
print(nlp.set_tools) #有@property,可以类似访问属性的来访问——方法变成属性
@classmethod
类方法装饰器,在类中的方法中使用这个关键字,可以不用实例化类,就能直接调用该方法。它不需要传入self参数,只需要传入cls参数。具体实例,由上面的代码改造一下:
class NLP(object):
model = 'bert'
task = 'classification'
tools = 'pytorch and python'
@classmethod
def set_model(cls):
return cls.model
if __name__ == '__main__':
print(NLP.set_model()) #这里并没有这样实例化NLP(),直接用NLP.来调用了
@classmethod
它可以使得类中的方法变为静态方法,不需要传入cls和self方法;调用的时候可以实例化类也可以不用实例化类,如下:
import time
class Date:
def __init__(self,year,month,day):
self.year=year
self.month=month
self.day=day
@staticmethod
def now():#不需要传入cls和self参数
t=time.localtime() #获取结构化的时间格式
return Date(t.tm_year,t.tm_mon,t.tm_mday) #新建实例并且返回
@staticmethod
def tomorrow():#不需要传入cls和self参数
t=time.localtime(time.time()+86400)#明天时间增加了24*3600秒
return Date(t.tm_year,t.tm_mon,t.tm_mday)
if __name__ == '__main__':
now = Date.now() #Date类没有实例化
tomorrow = Date.tomorrow()#Date类没有实例化
print(now.year,now.month,now.day)
print(tomorrow.year,tomorrow.month,tomorrow.day)
my_date = Date('2222','02','02')#Date类实例化了
print(my_date.year,my_date.month,my_date.day)
print(my_date.now().year)#Date类实例化了
四、装饰器的缺陷及解决方法
以上是就装饰器的优点作用和分类做了一个总结,那么装饰器有什么缺陷吗?python的装饰器会导致被修饰函数的__doc__,__name__等属性丢掉。__doc__是每一个对象的一个属性,它是用来记录这个对象的一些描述,例如函数的参数说明和功能说明,__name__则是输出函数或者对象的名称。
当使用装饰器的时候就会导致__doc__,__name__等属性缺失,这个看具体例子:
import time
def deco(f):
def wrapper(*args,**kwargs):
print('I am a decorator!,Founction run begin!')
sta = time.time()
f(*args,**kwargs)
end = time.time()
print('Founction run time is:',(end-sta))
print('Founction run finished!')
return wrapper
@deco
def do_somethin1(text):
'装饰器应用'
text += ':IG被滔博吊打!阿水牛逼!FPX被LGD吊打!冠军队是怎么回事?'
print(text)
def do_something2():
'ordinary function'
return None
if __name__ == '__main__':
do_somethin1('英雄联盟大事件!')
do_something2()
print('do_somethin1:', do_somethin1.__name__)
print('do_somethin1:', do_somethin1.__doc__)
print('*'*20)
print('do_somethin2:', do_something2.__name__)
print('do_somethin2:', do_something2.__doc__)
结果是:
I am a decorator!,Founction run begin!
英雄联盟大事件!:IG被滔博吊打!阿水牛逼!FPX被LGD吊打!冠军队是怎么回事?
Founction run time is: 2.6226043701171875e-06
Founction run finished!
do_somethin1: wrapper #期望的应该是:do_somethin1
do_somethin1: None #期望的应该是:装饰器应用
********************
do_somethin2: do_something2
do_somethin2: ordinary function
怎么解决这个缺陷呢?这里就需要用到另外的库了。直接使用functools.wraps,上述代码直接修改,wraps本质上也是一个装饰器。具体实例:
def deco(f):
@wraps(f)
def wrapper(*args,**kwargs):
print('I am a decorator!,Founction run begin!')
sta = time.time()
f(*args,**kwargs)
end = time.time()
print('Founction run time is:',(end-sta))
print('Founction run finished!')
return wrapper
结果如同我们期待的那样了:
I am a decorator!,Founction run begin!
英雄联盟大事件!:IG被滔博吊打!阿水牛逼!FPX被LGD吊打!冠军队是怎么回事?
Founction run time is: 2.86102294921875e-06
Founction run finished!
do_somethin1: do_somethin1
do_somethin1: 装饰器应用
********************
do_somethin2: do_something2
do_somethin2: ordinary function
其实这个缺陷个人认为是要看一定的业务场景来处理的,要是不需要被修饰函数的__doc__,__name__等属性,这个缺陷可以不处理;当然在有具体的需求场景的时候就是要处理的。
五、装饰器应用场景
具体的应用场景有哪些呢?它们主要是应用在如下的几个功能里面:
1、引入日志
2、函数时间统计
3、权限检测应用
我没有把装饰器用到过权限检测,给出一段别人总结的应用场景:否被授权去使用一个web应用的端点(endpoint)。它们被大量使用于Flask和Django web框架中。
from functools import wraps
def requires_auth(f): # f 就是我们需要装饰的函数,一看就是不带参数的装饰器
@wraps(f) # 新版python写法 @functools.wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
authenticate()
return f(*args, **kwargs)
return decorated # 该装饰器需相关配置才能运行,这里是截取代码展示应用
4、@tf.function
装饰器来将python代码转成图表示代码——这里完全是TensorFlow1.0和2.0之间一个差异,优劣互补的一个方法。
TensorFlow1.0中计算图代码:
g = tf.Graph() #初始化计算图
with g.as_default(): # 设置为默认计算图
a = tf.constant([[10,10],[11.,1.]])
x = tf.constant([[1.,0.],[0.,1.]])
b = tf.Variable(12.)
y = tf.matmul(a, x) + b # 描述计算图
init_op = tf.global_variables_initializer() # 待执行节点
with tf.Session() as sess: # 配置会话
sess.run(init_op) # 执行节点
print(sess.run(y)) # 输出结果
这种结构的静态图的特点是效率高,因为很多计算图优化的方法只能用在数据流图上。TensorFlow2.0则采用了类似pytorch的动态计算图,缺点就是效率低,但是代码易懂。
a = tf.constant([[10,10],[11.,1.]])
x = tf.constant([[1.,0.],[0.,1.]])
b = tf.Variable(12.)
y = tf.matmul(a, x) + b
print(y.numpy())
为了使用TensorFlow1.0的那种静态图,这里在TensorFlow2.0中使用@tf.function来装饰即可。
@tf.function
def f():
a = tf.constant([[10,10],[11.,1.]])
x = tf.constant([[1.,0.],[0.,1.]])
b = tf.Variable(12.)
y = tf.matmul(a, x) + b
print("PRINT: ", y)
tf.print("TF-PRINT: ", y)
return y
f()
写在最后,本文总结了python的装饰器的用法分类和优势,可能理解不是很深刻。但是内容也算上还比较全面,这个对于基础薄弱的人(比如我)还是有一定的作用的。加油!继续学习TensorFlow2.0对比着pytorch来学习。
参考文章