python-装饰器
一、概念
在不修改原函数的基础上,加上额外的功能。但是他的本质也是函数
二、原则
1、不修改被修饰的原代码
2、不修改被修饰的调用方式
三、装饰器的组成
装饰器 = 高阶函数 + 函数嵌套 + 闭包
1、高阶函数:
1.1、函数接收的参数是一个函数名
1.2、函数的返回值是一个函数名
1.3、满足上述条件任意一个,都可称之为高阶函数
2、函数嵌套:
函数里定义函数
3、闭包:
作用域
四、装饰器的实现过程
input: # 我们要在不修改foo函数的基础上加上统计运行时间
import time
def foo(): time.sleep(1) print('世界真奇妙') def timer(func): start_time = time.time() func() # 在函数里调用foo函数 end_time = time.time() print('foo函数运行时间是:', end_time - start_time) return func timer(foo) output: 世界真奇妙 foo函数运行时间是: 1.0000574588775635
这样我们实现了统计运行时间的功能,但是别忘了装饰器的一个原则不能修改函数的调用方式
input: import time # 我们要在不修改foo函数的基础上加上统计运行时间 def foo(): time.sleep(1) print('世界真奇妙') def timer(func): start_time = time.time() func() # 在函数里调用foo函数 end_time = time.time() print('foo函数运行时间是:', end_time - start_time) return func # timer(foo) # 我们实现了统计运行时间的功能,但是别忘了装饰器的一个原则不能修改函数的调用方式 foo = timer(foo) # timer函数的返回值是foo函数的内存地址,我们赋值给一个foo,然后再调用 print(foo) foo() output: 世界真奇妙 foo函数运行时间是: 1.0000574588775635 <function foo at 0x00000000004CC268> 世界真奇妙
到这里我们又实现了不修改调用方式,但是还有问题,就是foo函数被多执行了一遍!
那么我们有什么办法解决呢?
我们知道函数不加()是不执行的,那么我们可不可以将foo放到一个函数中呢?也就是嵌套函数
input: import time def foo(): time.sleep(1) print('世界真奇妙') def timer(func): def wrapper(): # 在timer函数中有定义一个wrapper函数 start_time = time.time() func() end_time = time.time() print('foo函数运行时间是:', end_time - start_time) return wrapper # 返回的是wrapper函数的内存地址 foo = timer(foo) # wrapper函数的内存地址 foo() # 执行wrapper output: 世界真奇妙 foo函数运行时间是: 1.0000572204589844
这时候我们已经基本实现了装饰器的功能,但是我们的调用方式还是有问题,我们调用的过程其实是两步
foo = timer(foo)
foo()
python提供了一个方法
在被装饰的函数上加 @timer ,@timer 相当于foo = timer(foo)
input: import time def timer(func): def wrapper(): start_time = time.time() func() end_time = time.time() print('foo函数运行时间是:', end_time - start_time) return wrapper # 返回的是wrapper函数的内存地址 @timer #相当于foo = timer(foo) def foo(): time.sleep(1) print('世界真奇妙') foo() output: 世界真奇妙 foo函数运行时间是: 1.0020573139190674
在foo()没有返回值的时候,这样是没有问题了,但是如果有返回值的时候呢?
input: import time def timer(func): def wrapper(): start_time = time.time() func() end_time = time.time() print('foo函数运行时间是:', end_time - start_time) return '这里是wrapper的返回值' return wrapper # 返回的是wrapper函数的内存地址 @timer def foo(): time.sleep(1) print('世界真奇妙') return '这里是foo的返回值' print(foo()) output: 世界真奇妙 foo函数运行时间是: 1.0000572204589844 这里是wrapper的返回值
我们发现,执行foo()实际上是返回了wrapper函数的返回值,那我们要怎么拿到foo()的返回值呢?
可以将foo()的返回值赋值给一个变量,然后wrapper返回这个变量
input: import time def timer(func): def wrapper(): start_time = time.time() res = func() end_time = time.time() print('foo函数运行时间是:', end_time - start_time) return res # 返回func的返回值 return wrapper # 返回的是wrapper函数的内存地址 @timer def foo(): time.sleep(1) print('世界真奇妙') return '这里是foo的返回值' print(foo()) output: 世界真奇妙 foo函数运行时间是: 1.0000569820404053 这里是foo的返回值
到这来还有一个问题,如果foo()需要传入参数呢?
input: import time def timer(func): def wrapper(): start_time = time.time() res = func() end_time = time.time() print('foo函数运行时间是:', end_time - start_time) return res # 返回func的返回值 return wrapper # 返回的是wrapper函数的内存地址 @timer def foo(name): # 需要传入一个参数 time.sleep(1) print('%s说世界真奇妙啊' %name) return '这里是foo的返回值' print(foo('马云')) output: Traceback (most recent call last): File "D:/pythonproject/work/venv/day20/装饰器.py", line 145, in <module> print(foo('马云')) TypeError: wrapper() takes 0 positional arguments but 1 was given
这来报了一个wrapper()不需要参数但是我们传入了一个,但是我们其实是想把参数入到给foo()的啊?
有什么办法解决呢?
闭包,我们可以给wrapper一个参数,在函数里的作用域了只要没有给这个参数重新赋值,那foo就可以接收到这个参数
input:
import time
def timer(func):
def wrapper(*args):
start_time = time.time()
res = func(args)
end_time = time.time()
print('foo函数运行时间是:', end_time - start_time)
return res # 返回func的返回值
return wrapper # 返回的是wrapper函数的内存地址
@timer
def foo(name): # 需要传入一个参数
time.sleep(1)
print('%s说世界真奇妙啊' % name)
return '这里是foo的返回值'
print(foo('马云'))
output: 马云说世界真奇妙啊 foo函数运行时间是: 1.0000288486480713 这里是foo的返回值
耶!!!可以传参了。但是问题又来了,如果是多个参数呢?
可变参数!!!
input: import time def timer(func): def wrapper(*args,**kargs): # 可变参数,是一个数组*('马云',24)和字典**{'gender': '男'} start_time = time.time() res = func(*args,**kargs) print(args) print(kargs) end_time = time.time() print('foo函数运行时间是:', end_time - start_time) return res # 返回func的返回值 return wrapper # 返回的是wrapper函数的内存地址 @timer def foo(name,age,gender): # 需要传入一个参数 time.sleep(1) print('%s在他%s岁的时候说,说%s人的世界真奇妙啊' %(name,age,gender)) return '这里是foo的返回值' print(foo('马云',24,gender='男')) output: 马云在他24岁的时候说,说男人的世界真奇妙啊 ('马云', 24) {'gender': '男'} foo函数运行时间是: 1.0002455711364746 这里是foo的返回值
五、实例
带验证功能装饰器
input: # 假设目前系统的注册用户如下: user_lis = [{'name': '刘备', 'passwd': '123'}, {'name': '张飞', 'passwd': '234'}, {'name': '关羽', 'passwd': '345'}] # 登录状态 user_dic = {'name': None, 'login': False} def auth_func(func): def wrapper(*args, **kwargs): if user_dic['name'] and user_dic['login']: # 先判断当前用户登录状态,如果已经登录,直接执行func() res = func(*args, **kwargs) return res username = input('用户名:') # 如果没有登录,则要求输入用户名和密码 passwd = input('密码:') for i in user_lis: # 遍历判断用户名、密码是否正确 if i['name'] == username and i['passwd'] == passwd: user_dic['name'] = username # 正确则修改用户名和状态 user_dic['login'] = True res = func(*args, **kwargs) # 并执行func return res else: print('用户名或密码错误') return wrapper @auth_func def index(): print('欢迎来到京东首页') @auth_func def shoping_car(name): print('%s京东购物车' % name) @auth_func def home(name): print('%s的个人中心' % name) index() shoping_car('用户') home('嘎嘎') output: 用户名:张飞 密码:234 欢迎来到京东首页 用户京东购物车 嘎嘎的个人中心
验证用户名和密码有不同的方式,如果我们要处理不同的认证方式,我们就需要给 auth_func()传入认证方式auth_type
那我们怎么给修饰传参呢?
还是用到闭包
我们在原来的基础的在外再加一个函数,带参数的修饰器
input: def auth(auth_type='file'): def auth_func(func): def wrapper(*args, **kwargs): if auth_type == 'file': print('这是文件验证') if user_dic['name'] and user_dic['login']: # 先判断当前用户登录状态,如果已经登录,直接执行func() res = func(*args, **kwargs) return res username = input('用户名:') # 如果没有登录,则要求输入用户名和密码 passwd = input('密码:') for i in user_lis: # 遍历判断用户名、密码是否正确 if i['name'] == username and i['passwd'] == passwd: user_dic['name'] = username # 正确则修改用户名和状态 user_dic['login'] = True res = func(*args, **kwargs) # 并执行func return res else: print('用户名或密码错误') elif auth_type == 'oracle': print('这是数据库验证,没有数据库认证个毛线,直接看吧') res = func(*args,**kwargs) return res else: print('其它验证方式,也可以直接看') res = func(*args,**kwargs) return res return wrapper return auth_func # @auth_func @auth(auth_type='file') # auth_func=auth(auth_type='filedb')-->@auth_func 附加了一个auth_type --->index=auth_func(index) def index(): print('欢迎来到京东首页') # @auth_func @auth(auth_type='oracle') def shoping_car(name): print('%s京东购物车' % name) # @auth_func @auth(auth_type='gg') def home(name): print('%s的个人中心' % name) index() shoping_car('用户') home('嘎嘎') output: 这是文件验证 用户名:z张飞 密码:232 用户名或密码错误 这是数据库验证,没有数据库认证个毛线,直接看吧 用户京东购物车 其它验证方式,也可以直接看 嘎嘎的个人中心
六、类的装饰器
1、类装饰器的基本原理
def desc(obj): print("执行desc") obj.x = 3 #添加类属性 obj.y = 4 return obj @desc # 等于Foo = desc(Foo) class Foo: pass print(Foo.__dict__)
运行结果
执行desc {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'x': 3, 'y': 4}
2、类装饰器添加类属性实例
def demo(**kwargs):
def wapper(obj):
for key,val in kwargs.items():
setattr(obj,key,val)
return obj
return wapper
@demo(name="江南七怪") #demo(name = "江南七怪") → 返回wapper并执行 wapper(Foo) Foo = wapper(Foo)
class Foo:
pass
print(Foo.__dict__)
运行结果
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'name': '江南七怪'}
3、类装饰器的应用
类装饰器与描述符想结合,实现实例化过程中传入参数的数据类型限制
class Type: def __init__(self, key, data_type): self.key = key self.data_type = data_type # 传入需要限制数据类型 def __get__(self, instance, owner): return instance.__dict__[self.key] def __set__(self, instance, value): if not isinstance(value, self.data_type): raise TypeError("传入的%s不是%s类型" % (value, self.data_type)) instance.__dict__[self.key] = value # 将实例的属性赋值 def __delete__(self, instance): print("运行delete") instance.__dict__.pop(self.key) def demo(**kwargs): def wapper(obj): for key, val in kwargs.items(): setattr(obj, key, Type(key, val)) # 调用描述符并添加属性 setattr(People,"name",Type("name",str)) return obj return wapper @demo(name=str, age=int, salary=int) class People: def __init__(self, name, age, salary): self.name = name self.age = age self.salary = salary p = People("张无忌", 25, 10000) print(People.__dict__) print(p.name, p.age, p.salary)
运行结果
{'__module__': '__main__', '__init__': <function People.__init__ at 0x00000000021BBE18>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None, 'name': <__main__.Type object at 0x00000000021CD2B0>, 'age': <__main__.Type object at 0x00000000021CD358>, 'salary': <__main__.Type object at 0x00000000021CD630>}
张无忌 25 10000
浙公网安备 33010602011771号