20 装饰器 decorator
在不改变原有函数代码,且保持原函数调用方法不变的情况下,给原函数增加新的功能或给类增加新的属性和方法。
20.1 装饰器的定义和原型
- 核心思想
用一个函数(或类)去装饰一个旧的函数(或类),造出一个新的函数(或新类) - 语法规则
在原有函数上加上 @ 符号,装饰器会把下面的函数当作参数传递到装饰器中,@符 又被称为 语法糖 - 应用场景
引入日志,函数执行时间的统计,
执行函数前的准备工作,执行函数后的处理工作,
权限校验,缓存等场景中
@outer
def func():
pass
20.1.1 装饰器的原型
利用闭包,把函数当作参数传递,并在函数内去调用传递进来的函数,并返回一个函数
# 定义外函数,接收一个函数作为参数
def outer(f):
# 定义内涵数,且在内函数中调用了外函数的参数
def inner():
print('this is inner1 from outer')
f()
print('this is inner2 from outer')
return inner
# # 普通函数
# def old():
# print('this is normal function')
#
# # old() # 作为普通函数调用
# old = outer(old) # outer返回了inner函数,赋值给了old
# old() # 此时调用old函数,等同于调用inner函数
# 改为装饰器
@outer # 此处使用的语法就是把outer作为装饰器 等同于 old = outer(old)
def old():
print('this is normal function')
old() # old函数经过 outer 装饰器进行了装饰,代码和调用方法不变,但是函数的功能发生了改变
20.1.2 装饰器的应用场景
统计函数执行时间
import time
# 定义一个统计函数执行时间的装饰器
def runtime(f):
def inner():
start = time.perf_counter()
f()
end = time.perf_counter() - start
print(f'the run time is {end}')
return inner
@runtime
def func():
for i in range(5):
time.sleep(1)
print(i,end='')
func()
20.2 装饰器的语法
20.2.1 装饰器的嵌套
1.普通装饰器的定义
# 定义装饰器
# 外函数
def outer(f):
# 内涵数
def inner():
print('find a girl')
# 内函数中调用外函数的形参-函数
f()
print('say goodbye')
# 外函数中返回内涵数
return inner
@outer
def love():
print('make a conversation')
love()
2.两个装饰器
# 外函数
def outer(f):
# 内涵数
def inner():
print('find a girl 3')
# 内函数中调用外函数的形参-函数
f()
print('say goodbye 4')
# 外函数中返回内涵数
return inner
def extent(f):
def inner():
print('extention 1')
f()
print('extention 2')
return inner
@extent
@outer
def love():
print('make a conversation 5')
love()
'''
1. 先使用离得近的outer装饰器,装饰love函数,返回一个inner函数
2. 再使用extent 装饰器,装饰上面返回的inner,又返回extent函数
调用love函数的顺序
love() == extent()
== 1
== inner()
== 3
==love()==5
== 4
== 2
装饰器的嵌套,先执行下面的,在执行上面的
@extent
@outer
'''
20.2.2 装饰带有参数的函数
装饰器带有形参,需要在内函数中定义形参,并传递给调用的形参
因为调用原函数等于调用内涵数
def outer(f):
def inner(n):
print(f'find a girl {n}')
f(n)
print('say goodbye')
return inner
@outer
def love(name):
print(f'make a conversation with {name}')
love('Oli')
20.2.3 装饰带有多参数的函数
def outer(f):
def inner(n,m,*args,**kwargs):
print(f'find a girl {n}')
f(n,m,*args,**kwargs)
print('say goodbye')
return inner
import time
@outer
def love(name,na,*args,**kwargs):
print(f'make a conversation with {name} and {na}')
print('went to next around',args)
print('also went to watch file',kwargs)
love('Oli','Mark','dance','KTV','drink',mov='insidious')
20.2.4 带有参数的装饰器
若装饰器需要有参数,那么给当前的装饰器套一个壳,用于接收装饰器的参数
- 装饰器壳的参数,可以用于在函数内去做流程控制
def ke(var):
def outer(f):
def inner1():
print('find a girl')
f()
def inner2():
print('find out its boy')
f()
if var =='rich':
return inner1
else:
return inner2
return outer
@ke('rich') # ke(var) == outer() == outer(love) == inner()
def love():
print('make a conversation')
love()
Django 身份验证系统 @login_required(login_url='/acounts/login/')
20.2.5 用类装饰器装饰函数
class Outer():
# 魔术方法:把该类的对象当作函数调用时,自动触发 obj()
def __call__(self,f):
self.f = f # 把传进来的函数作为对象的成员方法
return self.inner # 返回一个函数
# 在定义的需要返回的新方法中,进行装饰和处理
def inner(self,n):
print('1')
self.f(n)
print('2')
@Outer() # outer() == obj @obj == obj(love) == __call__(love) == inner()
def love(name):
print(f'make a conversation with {name}')
love('aa')
print(love) # 此时 love 属于 Outer类这个对象中的inner方法
# <bound method outer.inner of <__main__.outer object at 0x0000000001DEBE88>>
20.2.6 用类方法装饰函数
class Outer():
def inner(f):
Outer.f = f # 把传递进来的函数定义为类方法
return Outer.innn # 同时返回一个新的类方法,(形成闭包)
def innn(n):
print('1')
Outer.f(n)
print('2')
@Outer.inner # Outer.inner(love) == Outer.innn
def love(name):
print(f'make a conversation with {name}')
love('aa')
print(love) # <function Outer.inn at 0x0000000001E6A3A8>
Django web的认证系统 @permission_required('polls.can_vote',login_url='loginpage/')
到目前为止,以上所有形式的装饰器,包括 函数装饰器、类装饰器、类方法装饰器,都有一个共同特点:都是在给函数去进行装饰,增加功能
还有一种装饰器,专门装饰类,就是在类的定义的前面,使用@装饰器 这种语法
20.2.7 使用函数 装饰器装饰类
装饰器给函数进行装饰,目的是不改变函数调用和代码的情况下,给原函数增加新的功能
装饰器给类进行装饰,目的是不改变类的定义和调用的情况下,给原类增加新的成员(属性或方法)
# 定义函数,接收一个类,返回修改后的类
def extent(cls):
def func1():
print('this is new method from decorator')
cls.func1 = func1 # 把刚才定义的方法赋值给类
cls.ff = 'this is new function from decorator'
# 返回时,把追加的新成员的 类 返回去
return cls
@extent
class Demo():
def func(self):
print('this is func from class demo')
Demo.func(1) # 此时再调用的demo类是通过装饰器,更新过的Demo类
# 通过装饰器新增的成员
Demo.func1()
print(Demo.ff)
20.2.8 使用类 装饰器装饰类
class extent():
def __call__(self,cls):
# 把接收的类 赋值给当前对象,作为一个属性
self.cls = cls
# 返回一个函数
return self.newfunc
def newfunc(self):
self.cls.ff = 'this is new function from decorator'
self.cls.func2 = self.func2
# 在方法中 返回传递进来的类的实例化结果,obj
return self.cls()
def func2(self):
print('this is new method from decorator')
@extent() # extent() == obj == @obj(Demo) == __call__() == newfunc()
class Demo():
def func(self):
print('this is func from class demo')
obj = Demo() # Demo() == func() == obj
obj.func()
obj.func2()
print(obj.ff)
# 思考,此时的obj 是哪个类的对象
print(obj)
思考,如何用装饰器装饰带有返回值得函数