Loading

day 17 装饰器

1 无参装饰器

1.1 储备知识

1.1.1 *args, **kwargs

 def index(x,y):
	 print(x,y)

 def wrapper(*args,**kwargs):
	 index(*args,**kwargs) 

 wrapper(y=222,x=111)  # index(y=222,x=111)

1.1.2 名称空间与作用域:

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

1.1.3 函数对象:

可以把函数当做参数传入

可以把函数当做返回值返回

 def index():
 	return 123

 def foo(func):
	 return func

 foo(index)

1.1.4 函数的嵌套定义:

def outter(func):
    def wrapper():
   		 pass
    return wrapper

1.1.5 闭包函数

 def outter():
	 x=111
	 def wrapper():
			print (x)
	 return wrapper#

 f=outter()

1.1.6 传参的方式

  • 传参的方式一:通过参数的形式为函数体传值
def wrapper(x):
	 print(1)
	 print(2)
	 print(3)

 wrapper(1)
 wrapper(2)
 wrapper(3)

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

 def outter(x):
	 def wrapper():
			 print(x)
	 return wrapper                # return outter 内的 wrapper 那个函数的内地址

  f1=outter(1)
  f2=outter(2)
  f3=outter(3)
  wrapper=outter(1)

1.2 无参装饰器

1.2.1 什么是装饰器

​ '装饰'代指为被装饰对象添加新的功能,’器’代指器具/工具,装饰器与被装饰的对象均可以是任意可调用对象。概括地讲,装饰器的作用就是在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能。装饰器经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等应用场景,装饰器是解决这类问题的绝佳设计,有了装饰器,就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

​ 提示:可调用对象有函数,方法或者类,此处我们单以本章主题函数为例,来介绍函数装饰器,并且被装饰的对象也是函数。

器指的是工具,可以定义成成函数。装饰指的是为其他事物添加额外的东西点缀

合到一起的解释:

装饰器指的定义一个函数,该函数是用来为其他函数添加额外的功能

1.2.2 为何要用装饰器

​ 软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的。对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。

​ 软件包含的所有功能的源代码以及调用方式,都应该避免修改,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃,而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器。

开放封闭原则

开放:指的是对拓展功能是开放的

封闭:指的是对修改源代码是封闭的

装饰器就是在不修改被装饰器对象源代码以及调用方式的前提下为被装饰对象添加新功能

1.2.3 装饰器推导过程

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

     def index(x,y):
    	 time.sleep(3)
    	 print('index %s %s' %(x,y))
    
     index(111,222)
    
  • 解决方案一:失败

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

     import time
     def index(x,y):
    	 start=time.time()
    	 time.sleep(3)
    	 print('index %s %s' %(x,y))
    	 stop = time.time()
    	 print(stop - start)
    
     index(111,222)
    
  • 解决方案二:失败

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

     import time
     def index(x,y):
    	 time.sleep(3)
    	 print('index %s %s' %(x,y))
    
     start=time.time()
     index(111,222)
     stop=time.time()
     print(stop - start)
    
     start=time.time()
     index(111,222)
     stop=time.time()
     print(stop - start)
    
     start=time.time()
     index(111,222)
     stop=time.time()
     print(stop - start)
    
  • 解决方案三:失败

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

     import time
     def index(x,y):
    	 time.sleep(3)
    	 print('index %s %s' %(x,y))
    
     def wrapper():
    	 start=time.time()
    	 index(111,222)
    	 stop=time.time()
    	 print(stop - start)
    
     wrapper()
    
  • 方案三的优化一:将 index 的参数写活了

     import time
     def index(x,y,z):
    	 time.sleep(3)
    	 print('index %s %s %s' %(x,y,z))
    
     def wrapper(*args,**kwargs):
    	 start=time.time()
    	 index(*args,**kwargs)              # index(3333,z=5555,y=44444)
    	 stop=time.time()
    	 print(stop - start)
    
     wrapper(3333,4444,5555)
     wrapper(3333,z=5555,y=44444)
    
  • 方案三的优化二:在优化一的基础上把被装饰对象写活了,原来只能装饰 index

     import time
     
     def index(x,y,z):
    	 time.sleep(3)
    	 print('index %s %s %s' %(x,y,z))
    
     def home(name):
    	 time.sleep(2)
    	 print('welcome %s to home page' %name)
    
     def outter(func):
    	 def wrapper(*args,**kwargs):
    		 start=time.time()
    		 func(*args,**kwargs) # index 的内存地址()
    		 stop=time.time()
    		 print(stop - start)
    	 return wrapper
    
     index=outter(index)   # index=wrapper 的内存地址
     home=outter(home)   # home=wrapper 的内存地址
     home( 'egon' )
    
  • 方案三的优化三:将 wrapper 做的跟被装饰对象一模一样,以假乱真

     import time
     
     def index(x,y,z):
    	 time.sleep(3)
    	 print('index %s %s %s' %(x,y,z))
    
     def home(name):
    		 time.sleep(2)        
    		 print('welcome %s to home page' %name)
    		 
     def outter(func):
    	 def wrapper(*args,**kwargs):
    		 start=time.time()
    		 res=func(*args,**kwargs)
    		 stop=time.time()
    		 print(stop - start)
    		 return res
    	 return wrapper
    
     home=outter(home)
     res=home('egon') # res=wrapper('egon')
     print('返回值',res)
    
  • 语法糖:让你开心的语法

     import time
     def timmer(func):
    	 def wrapper(*args,**kwargs):
    		 start=time.time()
    		 res=func(*args,**kwargs)
    		 stop=time.time()
    		 print(stop - start)
    		 return res
    	 return wrapper
    
    # 在被装饰对象正上方的单独一行写@装饰器名字
    
    @timmer >>>> index=timmer(index)
     def index(x,y,z):
    	 time.sleep(3)
    	 print('index %s %s %s' %(x,y,z))
    
     @timmer >>>> home=timmer(ome)
     def home(name):
    	 time.sleep(2)
    	 print('welcome %s to home page' %name)
    
     index(x=1,y=2,z=3)
     home('egon')
    
  • 思考题(选做),叠加多个装饰器,加载顺序与运行顺序

    @deco1 		# index=deco1(deco2.wrapper 的内存地址)
     
    @deco2 		# deco2.wrapper 的内存地址=deco2(deco3.wrapper 的内存地址)
     
    @deco3 		# deco3.wrapper 的内存地址=deco3(index)
     def index():
    	 pass
    

1.2.4 总结无参装饰器模板

  • 模板

     def outter(func):
    	 def wrapper(*args,**kwargs):
    		 # 1  调用原函数
    		 # 2  为其增加新功能
    		 res=func(*args,**kwargs)
    		 return res
    	 return wrapper
    
def auth(func):      # 登录装饰器
	 def wrapper(*args,**kwargs):
         	  name=input('your name>>: ').strip()
		 pwd=input('your password>>: ').strip()
		 if name == 'egon' and pwd == '123':
			 res=func(*args,**kwargs)
			 return res
		 else:
			 print('账号密码错误')
			 return wrapper
	return wrapper

@auth
def index():
	print('from index')
	
index()
写装饰器三步走
1,先将装饰功能写出来
2,包装成为函数
3,将参数,函数写活
-------------------------------------------
把装饰器看做一个加工厂

装饰器就是原函数进来经过加工(装饰)之后返回一个外表相同内在不同的新函数
   所以装饰器的模板有两层函数:
   1:输入输出函数
   2:加工函数

2 有参装饰器

2.1 知识储备

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

 def outter(func):
	 def wrapper(*args,**kwargs):
		 res=func(*args,**kwargs)
		 return res
		 return wrapper

 @outter               #index=outter(index)    index=>wrapper
 def index(x,y):
	 print(x,y)
  • 偷梁换柱之后

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

2.2 有参装饰器

  • 山炮 1:

     def auth(func, db_type):
    	 def wrapper(*args, **kwargs):
    		 name=input('your name>>>: ').strip()
    		 pwd=input('your password>>>: ').strip()
    		 if db_type == 'file':
    			 print('基于文件的验证')
                             if name == 'egon' and pwd == '123':
                                 res = func(*args, **kwargs)
                                 return res
                             else:
                                 print('user or password error')
    		 elif db_type == 'mysql':
    			  print('基于 mysql 的验证')
    		 elif db_type == 'ldap':
    		 	   print('基于 ldap 的验证')
    		 else:
    			   print('不支持该 db_type')
    	 return wrapper
    
     def index(x,y):
    	 print('index->>%s:%s' %(x,y))
    
     def home(name):
    	 print('home->>%s' %name)
    
     def transfer():
    	 print('transfer')
    
     index=auth(index,'file')
     home=auth(home,'mysql')
     transfer=auth(transfer,'ldap')
     
     index(1,2)
     home('egon')
     transfer()
    
  • 山炮 2

     def auth(db_type):
    	 def deco(func):
    		 def wrapper(*args, **kwargs):
    			 name=input('your name>>>: ').strip()
    			 pwd=input('your password>>>: ').strip()
    			 if db_type == 'file':
    				 print('基于文件的验证')
                                     if name == 'egon' and pwd == '123':
                                         res = func(*args, **kwargs)
                                         return res
                                     else:
                                         print('user or password error')
    			 elif db_type == 'mysql':
    				 print('基于 mysql 的验证')
    			 elif db_type == 'ldap':
    				 print('基于 ldap 的验证')
    			 else:
    				 print('不支持该 db_type')
    		 return wrapper
    	 return deco
    
     deco=auth(db_type='file')
     @deco             #账号密码的来源是文件
     def index(x,y):
    	 print('index->>%s:%s' %(x,y))
    
     deco=auth(db_type='mysql')
     @deco            #账号密码的来源是数据库
     def home(name):
    	 print('home->>%s' %name)
    
     deco=auth(db_type='ldap')
     @deco            #账号密码的来源是 ldap
     def transfer():
    	 print('transfer')
    
     index(1,2)
     home('egon')
     transfer()
    
  • 最终 语法糖

    def auth(db_type):
    	def deco(func):
    		def wrapper(*args, **kwargs):
    			 name = input('your name>>>: ').strip()
    			 pwd = input('your password>>>: ').strip()
    			 if db_type == 'file':
    				 print('基于文件的验证')
                                     if name == 'egon' and pwd == '123':
                                         res = func(*args, **kwargs)            # index(1,2)
                                         return res
                                     else:
                                         print('user or password error')
    			 elif db_type == 'mysql': 
    			 	print('基于 mysql 的验证')
    			 elif db_type == 'ldap':
    				 print('基于 ldap 的验证')
    			 else:
    				 print('不支持该 db_type')
    		 return wrapper
    	 return deco
    
    @auth(db_type='file')       >>@deco >>index=deco(index) >>index=wrapper
    def index(x, y):
    	print('index->>%s:%s' % (x, y))
    
    @auth(db_type='mysql')     # @deco # home=deco(home) # home=wrapper
    def home(name):
    	print('home->>%s' % name)
    
    @auth(db_type='ldap')          # 账号密码的来源是 ldap
    def transfer():
    	 print('transfer')
    
    index(1, 2)
    home('egon')
    transfer()
    

2.3 有参装饰器模板

有参装饰器就是给无参装饰器包了一个包

 def 有参装饰器(x,y,z):
	 def outter(func):
		 def wrapper(*args, **kwargs):
			 res = func(*args, **kwargs)
			 return res
		 return wrapper
	 return outter
	 
 @有参装饰器(1,y=2,z=3)
 def 被装饰对象():
	 pass

3 补充

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

手动将原函数的属性赋值给 wrapper 函数
1、函数 wrapper.__ name__ = 原函数.__ name__
2、函数 wrapper.__ doc __ = 原函数. __ doc __

from functools import wraps
  
def outter(func):
	 @wraps(func)
	 def wrapper(*args, **kwargs):
		 """这个是主页功能"""
		 res = func(*args, **kwargs) # res=index(1,2)
		 return res
 		 wrapper.__name__ = func.__name__  
		 wrapper.__doc__ = func.__doc__
	 return wrapper
	 
@outter # index=outter(index)
def index(x,y):
 	"""这个是主页功能"""
	print(x,y)
	print(index.__name__)
	print(index.__doc__) #help(index)

index(1,2) # wrapper(1,2) 

4 叠加多个装饰器

4.1 叠加多个装饰器的加载、运行分析

 def deco1(func1): 		  # func1 = wrapper2 的内存地址
	 def wrapper1(*args,**kwargs):
		 print('正在运行===>deco1.wrapper1')
		 res1=func1(*args,**kwargs)
		 return res1
	 return wrapper1

 def deco2(func2): 		    # func2 = wrapper3 的内存地址
	 def wrapper2(*args,**kwargs):
		 print('正在运行===>deco2.wrapper2')
		 res2=func2(*args,**kwargs)
		 return res2
	 return wrapper2

 def deco3(x):
	 def outter3(func3): 	# func3=被装饰对象 index 函数的内存地址
		 def wrapper3(*args,**kwargs):
			 print('正在运行===>deco3.outter3.wrapper3')
			 res3=func3(*args,**kwargs)
			 return res3
		 return wrapper3
	 return outter3

4.2 加载顺序自下而上

@deco1 		>>> index=deco1(wrapper2 的内存地址) ===> index=wrapper1 的内存地址

@deco2 		 >>>index=deco2(wrapper3 的内存地址) ===> index=wrapper2 的内存地址

@deco3(111)     ===>@outter3===> index=outter3(index) ===> index=wrapper3 的内存地址
def index(x,y):
	print('from index %s:%s' %(x,y))

# 执行顺序自上而下的,即 wraper1-》wrapper2-》wrapper3
index(1,2) # wrapper1(1,2)
======================wrap1
   ==================wrap2
      ==============wrap3
         ==========index
      ==============wrap3
   ==================wrap2
======================wrap1

叠加多个装饰器就跟套饼一样
装饰的时候是自下而上,由里而外。
运行时自上而下,由外至里。
posted @ 2021-12-04 22:36  maju  阅读(23)  评论(0)    收藏  举报