一、储备知识

1、 args, *kwargs

  1. def index(x,y):
  2. print(x,y)
  3. def wrapper(*args,**kwargs):
  4. index(*args,**kwargs) #
  5. # index(y=222,x=111)
  6. wrapper(y=222,x=111)

2、名称空间与作用域

名称空间的的”嵌套”关系是在函数定义阶段,即检测语法的时候确定的

3、函数对象

可以把函数当做参数传入
可以把函数当做返回值返回

  1. def index():
  2. return 123
  3. def foo(func):
  4. return func
  5. foo(index)

4、函数的嵌套定义

  1. def outter(func):
  2. def wrapper():
  3. pass
  4. return wrapper

闭包函数

  1. def outter():
  2. x=111
  3. def wrapper():
  4. x
  5. return wrapper
  6. f=outter()

传参的方式一

通过参数的形式为函数体传值

  1. def wrapper(x):
  2. print(1)
  3. print(2)
  4. print(3)
  5. x
  6. wrapper(1)
  7. wrapper(2)
  8. wrapper(3)

传参的方式二

通过闭包的方式为函数体传值

  1. def outter(x):
  2. def wrapper():
  3. print(1)
  4. print(2)
  5. print(3)
  6. x
  7. return wrapper # return outter内的wrapper那个函数的内地址
  8. f1=outter(1)
  9. f2=outter(2)
  10. f3=outter(3)
  11. wrapper=outter(1)

二、无参装饰器

1、什么是装饰器

  1. 器指的是工具,可以定义成成函数<br /> 装饰指的是为其他事物添加额外的东西点缀<br /> <br /> 合到一起的解释:<br /> 装饰器指的定义一个函数,该函数是用来为其他函数添加额外的功能

2、为何要用装饰器

  1. 开放封闭原则<br /> 开放:指的是对拓展功能是开放的<br /> 封闭:指的是对修改源代码是封闭的<br /> <br /> 装饰器就是在不修改被装饰器对象源代码以及调用方式的前提下为被装饰对象添加新功能

3、如何用

需求:在不修改index函数的源代码以及调用方式的前提下为其添加统计运行时间的功能

  1. import time
  2. def index(x,y):
  3. time.sleep(3)
  4. print('index %s %s' %(x,y))
  5. index(111,222)
  6. index(y=111,x=222)
  7. index(111,y=222)

解决方案一:失败
问题:没有修改被装饰对象的调用方式,但是修改了其源代码

  1. import time
  2. def index(x,y):
  3. start=time.time()
  4. time.sleep(3)
  5. print('index %s %s' %(x,y))
  6. stop = time.time()
  7. print(stop - start)
  8. index(111,222)

解决方案二:失败
问题:没有修改被装饰对象的调用方式,也没有修改了其源代码,并且加上了新功能,但是代码冗余

  1. import time
  2. def index(x,y):
  3. time.sleep(3)
  4. print('index %s %s' %(x,y))
  5. start=time.time()
  6. index(111,222)
  7. stop=time.time()
  8. print(stop - start)
  9. start=time.time()
  10. index(111,222)
  11. stop=time.time()
  12. print(stop - start)
  13. start=time.time()
  14. index(111,222)
  15. stop=time.time()
  16. print(stop - start)

解决方案三:失败
问题:解决了方案二代码冗余问题,但带来一个新问题即函数的调用方式改变了

  1. import time
  2. def index(x,y):
  3. time.sleep(3)
  4. print('index %s %s' %(x,y))
  5. def wrapper():
  6. start=time.time()
  7. index(111,222)
  8. stop=time.time()
  9. print(stop - start)
  10. wrapper()

方案三的优化一:将index的参数写活了

  1. import time
  2. def index(x,y,z):
  3. time.sleep(3)
  4. print('index %s %s %s' %(x,y,z))
  5. def wrapper(*args,**kwargs):
  6. start=time.time()
  7. index(*args,**kwargs) # index(3333,z=5555,y=44444)
  8. stop=time.time()
  9. print(stop - start)
  10. # wrapper(3333,4444,5555)
  11. # wrapper(3333,z=5555,y=44444)

方案三的优化二:在优化一的基础上把被装饰对象写活了,原来只能装饰index

  1. import time
  2. def index(x,y,z):
  3. time.sleep(3)
  4. print('index %s %s %s' %(x,y,z))
  5. def home(name):
  6. time.sleep(2)
  7. print('welcome %s to home page' %name)
  8. def outter(func):
  9. # func = index的内存地址
  10. def wrapper(*args,**kwargs):
  11. start=time.time()
  12. func(*args,**kwargs) # index的内存地址()
  13. stop=time.time()
  14. print(stop - start)
  15. return wrapper
  16. index=outter(index) # index=wrapper的内存地址
  17. home=outter(home) # home=wrapper的内存地址
  18. home('egon')
  19. # home(name='egon')

方案三的优化三:将wrapper做的跟被装饰对象一模一样,以假乱真

  1. import time
  2. def index(x,y,z):
  3. time.sleep(3)
  4. print('index %s %s %s' %(x,y,z))
  5. def home(name):
  6. time.sleep(2)
  7. print('welcome %s to home page' %name)
  8. def outter(func):
  9. def wrapper(*args,**kwargs):
  10. start=time.time()
  11. res=func(*args,**kwargs)
  12. stop=time.time()
  13. print(stop - start)
  14. return res
  15. return wrapper
  16. # 偷梁换柱:home这个名字指向的wrapper函数的内存地址
  17. home=outter(home)
  18. res=home('egon') # res=wrapper('egon')
  19. print('返回值--》',res)

大方向:如何在方案三的基础上不改变函数的调用方式

语法糖:让你开心的语法

  1. import time
  2. # 装饰器
  3. def timmer(func):
  4. def wrapper(*args,**kwargs):
  5. start=time.time()
  6. res=func(*args,**kwargs)
  7. stop=time.time()
  8. print(stop - start)
  9. return res
  10. return wrapper
  11. # 在被装饰对象正上方的单独一行写@装饰器名字
  12. @timmer # index=timmer(index)
  13. def index(x,y,z):
  14. time.sleep(3)
  15. print('index %s %s %s' %(x,y,z))
  16. @timmer # home=timmer(ome)
  17. def home(name):
  18. time.sleep(2)
  19. print('welcome %s to home page' %name)
  20. return 8888
  21. index(x=1,y=2,z=3)
  22. res=home('egon')
  23. print('返回值--》',res)

思考题(选做),叠加多个装饰器,加载顺序与运行顺序
# @deco1 # index=deco1(deco2.wrapper的内存地址)
# @deco2 # deco2.wrapper的内存地址=deco2(deco3.wrapper的内存地址)
# @deco3 # deco3.wrapper的内存地址=deco3(index)
# def index():
# pass

4、总结无参装饰器模板

  1. def outter(func):
  2. def wrapper(*args,**kwargs):
  3. # 1、调用原函数
  4. # 2、为其增加新功能
  5. res=func(*args,**kwargs)
  6. return res
  7. return wrapper

例子:

  1. def auth(func):
  2. def wrapper(*args,**kwargs):
  3. # 1、调用原函数
  4. # 2、为其增加新功能
  5. name=input('your name>>: ').strip()
  6. pwd=input('your password>>: ').strip()
  7. if name == 'egon' and pwd == '123':
  8. res=func(*args,**kwargs)
  9. return res
  10. else:
  11. print('账号密码错误')
  12. return wrapper
  13. @auth
  14. def index():
  15. print('from index')
  16. index()

偷梁换柱,即将原函数名指向的内存地址偷梁换柱成wrapper函数
所以应该将wrapper做的跟原函数一样才行

5、设置成一模一样

  1. from functools import wraps
  2. def outter(func):
  3. @wraps(func) #一模一样的属性
  4. def wrapper(*args, **kwargs):
  5. """这个是主页功能"""
  6. res = func(*args, **kwargs) # res=index(1,2)
  7. return res
  8. # 手动将原函数的属性赋值给wrapper函数
  9. # 1、函数wrapper.__name__ = 原函数.__name__
  10. # 2、函数wrapper.__doc__ = 原函数.__doc__
  11. # wrapper.__name__ = func.__name__
  12. # wrapper.__doc__ = func.__doc__
  13. return wrapper
  14. @outter # index=outter(index)
  15. def index(x,y):
  16. """这个是主页功能"""
  17. print(x,y)
  18. print(index.__name__)
  19. print(index.__doc__) #help(index)

三、有参装饰器

1、 知识储备

由于语法糖@的限制,outter函数只能有一个参数,并且该才是只用来接收被装饰对象的内存地址

  1. def outter(func):
  2. # func = 函数的内存地址
  3. def wrapper(*args,**kwargs):
  4. res=func(*args,**kwargs)
  5. return res
  6. return wrapper
  7. # @outter # index=outter(index) # index=>wrapper
  8. @outter # outter(index)
  9. def index(x,y):
  10. print(x,y)

偷梁换柱之后
index的参数什么样子,wrapper的参数就应该什么样子
index的返回值什么样子,wrapper的返回值就应该什么样子
index的属性什么样子,wrapper的属性就应该什么样子==》from functools import wraps

山炮玩法:

  1. def auth(func,db_type):
  2. def wrapper(*args, **kwargs):
  3. name=input('your name>>>: ').strip()
  4. pwd=input('your password>>>: ').strip()
  5. if db_type == 'file':
  6. print('基于文件的验证')
  7. if name == 'egon' and pwd == '123':
  8. res = func(*args, **kwargs)
  9. return res
  10. else:
  11. print('user or password error')
  12. elif db_type == 'mysql':
  13. print('基于mysql的验证')
  14. elif db_type == 'ldap':
  15. print('基于ldap的验证')
  16. else:
  17. print('不支持该db_type')
  18. return wrapper
  19. # @auth # 账号密码的来源是文件
  20. def index(x,y):
  21. print('index->>%s:%s' %(x,y))
  22. # @auth # 账号密码的来源是数据库
  23. def home(name):
  24. print('home->>%s' %name)
  25. # @auth # 账号密码的来源是ldap
  26. def transfer():
  27. print('transfer')
  28. index=auth(index,'file')
  29. home=auth(home,'mysql')
  30. transfer=auth(transfer,'ldap')
  31. # index(1,2)
  32. # home('egon')
  33. # transfer()

山炮二

  1. def auth(db_type):
  2. def deco(func):
  3. def wrapper(*args, **kwargs):
  4. name=input('your name>>>: ').strip()
  5. pwd=input('your password>>>: ').strip()
  6. if db_type == 'file':
  7. print('基于文件的验证')
  8. if name == 'egon' and pwd == '123':
  9. res = func(*args, **kwargs)
  10. return res
  11. else:
  12. print('user or password error')
  13. elif db_type == 'mysql':
  14. print('基于mysql的验证')
  15. elif db_type == 'ldap':
  16. print('基于ldap的验证')
  17. else:
  18. print('不支持该db_type')
  19. return wrapper
  20. return deco
  21. deco=auth(db_type='file')
  22. @deco # 账号密码的来源是文件
  23. def index(x,y):
  24. print('index->>%s:%s' %(x,y))
  25. deco=auth(db_type='mysql')
  26. @deco # 账号密码的来源是数据库
  27. def home(name):
  28. print('home->>%s' %name)
  29. deco=auth(db_type='ldap')
  30. @deco # 账号密码的来源是ldap
  31. def transfer():
  32. print('transfer')
  33. index(1,2)
  34. home('egon')
  35. transfer()

语法糖

  1. def auth(db_type):
  2. def deco(func):
  3. def wrapper(*args, **kwargs):
  4. name = input('your name>>>: ').strip()
  5. pwd = input('your password>>>: ').strip()
  6. if db_type == 'file':
  7. print('基于文件的验证')
  8. if name == 'egon' and pwd == '123':
  9. res = func(*args, **kwargs) # index(1,2)
  10. return res
  11. else:
  12. print('user or password error')
  13. elif db_type == 'mysql':
  14. print('基于mysql的验证')
  15. elif db_type == 'ldap':
  16. print('基于ldap的验证')
  17. else:
  18. print('不支持该db_type')
  19. return wrapper
  20. return deco
  21. @auth(db_type='file') # @deco # index=deco(index) # index=wrapper
  22. def index(x, y):
  23. print('index->>%s:%s' % (x, y))
  24. @auth(db_type='mysql') # @deco # home=deco(home) # home=wrapper
  25. def home(name):
  26. print('home->>%s' % name)
  27. @auth(db_type='ldap') # 账号密码的来源是ldap
  28. def transfer():
  29. print('transfer')
  30. # index(1, 2)
  31. # home('egon')
  32. # transfer()

2、有参装饰器模板

  1. def 有参装饰器(x,y,z):
  2. def outter(func):
  3. def wrapper(*args, **kwargs):
  4. res = func(*args, **kwargs)
  5. return res
  6. return wrapper
  7. return outter
  8. @有参装饰器(1,y=2,z=3)
  9. def 被装饰对象():
  10. pass