装饰器 decorator
在不改变原函数代码,且保持原函数调用方法不变的情况下,给原函数增加新的功能,或者给类增加属性和方法。
装饰器定义
核心思想:用一个函数(或者类)去装饰一个旧函数(或者类),造出一个新函数(或者新类)
语法规则:在原有的函数上加上 @符,装饰器会把下面的函数当作参数传递到装饰器中,@符又被成为 语法糖
应用场景:引入日志,函数执行时间的统计,执行函数前的准备工作,执行函数后的处理工作,权限校验,缓存等
###1.装饰器原型(闭包)
利用闭包,把函数当作参数传递,并且在函数内去调用传递进来的函数,并返回一个函数。
#定义外函数,接收一个函数作为参数
def outer(f):
#定义内函数,并且在内函数中调用了外函数的参数
def inner():
print('456')
f()
return inner #外函数的返回值为内函数,此时内函数就是闭包函数
#定义普通函数
def func():
print('123')
func() #作为普通函数直接调用
func = outer(func) #outer返回inner函数,并赋值给了func
func() #此时再调用func函数时,就等于调用了inner函数
>>>123
456
123
改为装饰器用法:
#定义外函数,接收一个函数作为参数
def outer(f):
#定义内函数,并且在内函数中调用了外函数的参数
def inner():
print('456')
f()
return inner #外函数的返回值为内函数,此时内函数就是闭包函数
@outer #此处使用的@outer的语法就是把outer作为了装饰器,等同于 func = outer(func)
#此时上面那一大块函数就变成了装饰器!
def func():
print('123')
func() #func函数经过了outer装饰器进行了装饰,代码和调用方法不变,但是函数的功能发生了改变
>>>456
123
2.装饰器的应用:统计函数的执行时间
# 装饰器应用场景-统计函数执行时间
import time
# 定义一个统计函数执行时间的 装饰器
def outer(f):
#定义内函数,并且在内函数中调用了外函数的参数
def inner():
start = time.perf_counter()
f()
end = time.perf_counter() - start
print(f'函数调用时间为:{end}')
return inner #外函数的返回值为内函数,此时内函数就是闭包函数
@outer #此处使用的@outer的语法就是把outer作为了装饰器,等同于 func = outer(func)
def func():
for i in range(5):
print(i,end = ' ')
time.sleep(1)
func()
>>>0 1 2 3 4 函数调用时间为:5.0028037
3.装饰器嵌套语法
# 1.定义装饰器
# 外函数
def outer(f):
#内函数
def inner():
print('3')
f() # 在内函数中调用外函数中的行参-函数
print('4')
# 在外函数中返回内函数
return inner
# 2.再定义一个装饰器
def kuozhan(f):
def kzinner():
print('1')
f()
print('2')
return kzinner
# 3. 装饰器的嵌套 先执行下面的,再执行上面的。
@kuozhan # (2.)再使用上面的 kuozhan 装饰器,装饰 上一次返回的 inner 函数,又返回了 kzinner 函数
@outer # (1.)先使用离得近的 outer装饰器 装饰func函数,返回了一个 inner函数
def func():
print('5')
func()
>>>1
3
5
4
2
''' 过程的解析
1 先使用离得近的 outer装饰器 装饰func函数,返回了一个 inner函数
2 再使用上面的 kuozhan 装饰器,装饰 上一次返回的 inner 函数,又返回了 kzinner 函数
最后在调用func函数的时候是怎么执行的:
func() == kzinner()
===> 1
===> inner()
===> 3
===> func() ===> 5
===> 4
===> 2
'''
4.对带有参数的函数进行装饰
# 对带有参数的函数进行装饰
# 定义装饰器
def outer(f):
# 如果装饰器带有参数的函数,需要在内函数中定义行参,并传递给调用的函数。
# 因为调用原函数等于调用内函数;
def inner(n):
print('调用原函数,最终执行的是内函数')
f(n)
return inner
# 有参数的函数
@outer
def func(n):
print(f'带有参数{n}的函数')
func(1)
>>>调用原函数,最终执行的是内函数
带有参数1的函数
'''
func() ==> inner() 调用func等于调用inner
func(...) ==> inner(...) func里有...参数,那么inner里也得有
inner(...) ==> func(...) inner里的各种参数才能传给内函数中的func,才能实现原函数func的功能
'''
5.对多参数的函数进行装饰
# 对多参数的函数进行装饰
# 定义装饰器
def outer(f):
# 如果装饰器带有参数的函数,需要在内函数中定义行参,并传递给调用的函数。
# 因为调用原函数等于调用内函数;
def inner(a,b,*args,**kwargs):
print('和装饰带单个参数的函数类似啦')
f(a,b,*args,**kwargs)
return inner
@outer
def func(n,m,*args,**kwargs):
print(f'带有参数{n}和{m}的函数')
print(f'想吃{args}')
print(f'这里就是想复习一下{kwargs}')
func(1,'abc','炸鸡','牛蛙',zxc = '函数里的形参们',abc = '今天网实在不太好')
>>>和装饰带单个参数的函数类似啦
带有参数1和abc的函数
想吃('炸鸡', '牛蛙')
这里就是想复习一下{'zxc': '函数里的形参们', 'abc': '今天网实在不太好'}
'''
func() ==> inner() 调用func等于调用inner
func(...) ==> inner(...) func里有...参数,那么inner里也得有
inner(...) ==> func(...) inner里的各种参数才能传给内函数中的func,才能实现原函数func的功能
'''
6.带有参数的装饰器
会遇到带有参数的装饰器,例如Django框架中的 @login_required(login_url=’/accounts/login/’)
# 带有参数的装饰器
# 如果装饰器需要有参数,那么给当前的装饰器套一个壳,用于接收装饰器的参数
def ke(n):
def outer(i):
def inner1():
print('你现在看到的装饰器的参数为0')
i()
def inner2():
print('你现在看到的装饰器的参数为1')
i()
# 装饰器壳的参数,用于在外函数内去做流程控制
if n == 0:
return inner1
else:
return inner2
return outer
@ke(1) # ke(var) ==> outer() ==> outer(func) ==> inner()
def func():
print('带有参数的装饰器需要有个壳')
func()
>>>你现在看到的装饰器的参数为1
带有参数的装饰器需要有个壳
7.用类装饰器装饰函数
#老师讲的方法
#类装饰器来装饰函数
class Outer():
# 魔术方法:当把该类的对象当作函数调用时,自动触发
def __call__(self,funcc):
self.func = funcc
return self.inner
def inner(self,i):
print('老师讲的方法')
self.func(i)
@Outer() #@Outer() ==> @obj ==> obj(func) ==> __call__(func) ==> inner()
def func(n):
print(f'{n}终于快学完了!')
func('cc')
print(func) #此时的 func就是属于Outer类的对象中的inner方法
'''
print(func)的结果为:
<bound method Outer.inner of <__main__.Outer object at 0x000001D672DEFCC8>>
此时的 func就是属于Outer类的对象中的inner方法
'''
#自己一开始琢磨的方法
#类装饰器来装饰函数
class Outer():
# 魔术方法:当把该类的对象当作函数调用时,自动触发
def __call__(self,n):
def inner(a):
print('把__call__当成外函数')
n(a)
print('此时n是原函数的名字,a是原函数的形参')
return inner
@Outer()
def func(n):
print(f'{n}终于快学完了!')
func('cc')
'''
Outer() ==> 等于实例化了一个对象obj
所以:@Outer() ==> @obj
@obj ==> obj(func) 把对象当函数调用时会自动触发Outer类里的__call__魔术方法
所以:==> __call__(func) ==> inner() 此时的__call__就是外函数
'''
print(func)
'''
此时print(func)的结果为:
<function Outer.__call__.<locals>.inner at 0x000001F9C2D95B88>
'''
8.用类方法装饰函数
class Outer():
def outer(funcc): #外函数
Outer.func = funcc # 把传递进来的函数定义为类方法
return Outer.inner # 返回一个新的类方法
def inner(): #内函数
print('老师讲的方法')
Outer.func()
@Outer.outer # Outer.outer(func) ==> Outer.inner
def func():
print('终于快学完了!')
func() # func() ==> Outer.inner()
#自己琢磨的
#不过这样好像就没有用类的必要了,可以直接用函数当装饰器
class Outer():
def outer(funcc): #外函数
def inner(): #内函数
funcc()
print('这样也可以')
return inner
@Outer.outer
def func():
print('终于快学完了!')
func()
到目前为止以上所以形式的装饰器,包括 函数装饰器,类装饰器,类方法装饰器,都有一个共同特点:都是在给函数去进行装饰,增加功能。还有一种装饰器,是专门装饰类的:
##用装饰器装饰类
在类的定义的前面使用 @装饰器 这种语法
@装饰器
class Demo():
pass
装饰器给函数进行装饰,目的是不改变函数调用和代码的情况下给原函数增加了新的功能。
装饰器给类进行装饰,目的是不改变类的定义和调用的情况下给类增加新的成员(属性或方法)。
9.用函数装饰器装饰类
# 定义函数,接收一个类。返回修改后的类
def kuozhan(cls):
def new():
print('装饰器中追加的方法')
cls.new = new # 把刚才定义的方法赋值给类
cls.name = '装饰器中追加的属性'
# 返回时,把追加了类新成员的 类 返回去
return cls
@kuozhan # @kuozhan ==> kuozhan(Demo) ==> cls ==> Demo
class Demo():
def func():
print('这是一个绑定类方法')
Demo.func() # 此时在调用的Demo类是通过装饰器更新过的Demo类
Demo.new()
print(Demo.name)
>>>这是一个绑定类方法
装饰器中追加的方法
装饰器中追加的属性
###10.使用类装饰器装饰类
class KuoZhan():
def __call__(self, cls):
# 把接收的类,赋值给当前对象,作为一个属性
self.cls = cls
# 返回一个函数
return self.newfunc
def newfunc(self):
self.cls.name = '我是在类装饰器中追加的新属性 name'
self.cls.func2 = self.func2
# 返回传递进来的类的实例化结果,obj
return self.cls()
def func2(self):
print('我是在类装饰器中追加的新方法 func2')
@KuoZhan() # KuoZhan() ==> obj ==> @obj(Demo) ==> __call__(Demo) ==> newfunc
class Demo():
def func(self):
print('我是Demo类中定义的func方法')
obj = Demo() # @KuoZhan() ==> @obj ==> obj(Demo) ==> __call__(Demo) ==> newfunc
obj.func()
obj.func2()
print(obj.name)
>>>我是Demo类中定义的func方法
我是在类装饰器中追加的新方法 func2
我是在类装饰器中追加的新属性 name
# 此时的obj依然是Demo类的实例化对象,只不过经过装饰后,增加了新的属性和方法