设为首页 收藏本站
查看: 1775|回复: 0

[经验分享] Python16 装饰器

[复制链接]

尚未签到

发表于 2018-8-6 12:51:01 | 显示全部楼层 |阅读模式
装饰器概念
  装饰器本质是函数,也是通过def定义的。
  装饰器的功能是用来装饰其他函数的,也就是为其他函数添加附加功能
  装饰器有独特的原则:
  

不能修改被装饰函数的源代码 (源代码没有被修改)  

  
不能修改被装饰函数的调用方式 (运行函数没有被修改)
  

  总结:就是源代码不能被修改。
  

装饰器对于被装饰的函数,相当于完全透明的,函数的源代码不能被修改(某些在线的系统如果被修改原代码,不知道会出现什么问题),但又想加些功能,但原函数又不知道自己被新加了功能(没有感知到),这就叫装饰器。  

  学习装饰器需要掌握的知识:
  

1.函数即‘变量’  

  
2.高阶函数
  

  
3.嵌套函数
  

  高阶函数+嵌套函数=装饰器

函数即‘变量’
  

def bar():  print ('in the bar')
  

  
def foo():
  print ('in the foo')
  bar()
  

  
foo()
  

DSC0000.png

  函数中调用另一个函数,执行结果!
  函数即‘变量’:定义一个函数,相当于将一个函数体赋值给了一个函数名
  比如x = 1,就是将1赋值给了x
  

def bar():  print ('in the bar')
  

  
就是将print ('in the bar')这个函数体赋值给了bar()
  

  内存缓存:
  

当x = 1时,1这个值会缓存到内存中,x只是从内存中引用了1这个值,如果此时y = x,相当于1被多次引用,也就是y也引用了内存中的1。 这个1只有在程序中del 了变量或程序执行结束后才会从内存中剔除,否则就会一直存在内存中。  当然如果有多个变量引用这个1的话,单单del 一个变量是不会从内存中剔除1这个值,因为还有其他变量在引用1,所以只有当1没有被任何变量引用时,才会从内存中剔除。  

  
函数与变量的概念是一样的,函数体会一直缓存在内存中,除非函数名被删除或程序执行完毕后内存会回收函数体的数据。
  

  变量:
  

x = 10  
x = 1
  
y = x
  
x = 2
  
print (y)
  

DSC0001.png

  print y的执行结果等于1,也就是y被赋了上面x的值,y = x时,y会从程序的第1行向下去找x来赋值,不会从y下面去找x来赋值,如果y上面存在多个x变量的话,那么就通过离y最近的x来赋值。
DSC0002.png

DSC0003.png

  可以看到y是不会再其下面去找x变量来赋值的。
  函数:
  

def bar():  print ('in the bar')
  

  
def foo():
  print ('in the foo')
  bar()
  

  
foo()
  

DSC0004.png

  函数与变量相似,只不过函数是在调用函数时的上面如果存在就可以,也就是相关的调用变量或函数在foo()的代码上面存在就会被正常引用,因为foo()相当于函数的一部分。
  

def foo():  print ('in the foo')
  bar()
  
def bar():
  print ('in the bar')
  

  
foo()
  

DSC0005.png

  可以看到函数bar()只要在调用foo()函数之前就可以被正常的引用。
  

def foo():  print ('in the foo')
  bar()
  

  
foo()
  

  
def bar():
  print ('in the bar')
  

DSC0006.png

  调用函数foo()时, print ('in the foo')可以被正常执行,但是bar()函数在调用foo()的下面,不能被正常引用。

高阶函数
  满足了两个条件就可以被称之为高阶函数:
  

1.把一个函数当做实参传给另一个函数。  
2.返回值中包含函数名
  

1.把一个函数当做实参传给另一个函数。
  

def bar():  print ('in the bar')
  

  
def test1(func):
  print (func)
  

  
test1(bar)   #括号中如果不调用bar() 却只调用bar的话,print (func)相当于将bar这个函数的内存地址给打印出来,而不是执行bar()这个函数;
  

DSC0007.png

  

def bar():  print ('in the bar')
  

  
def test1(func):
  print (func)
  func()
  

  
#可以通过func()来调用bar()这个函数,因为此时func()被赋值了bar(),此时func()等于bar(),类似x=1,y=x的概念。
  

  
test1(bar)
  

DSC0008.png

  不能修改被装饰函数的源代码 (源代码没有被修改):
  

import time  

  
def bar():            #被装饰的函数
  time.sleep(3)
  print ('in the bar')
  

  
def test1(func):    #装饰器
  start_time = time.time()
  func()
  stop_time = time.time()
  print ('the func run time is %s' %(stop_time - start_time))  #在不改变bar()这个函数的情况下,给bar()函数添加功能
  

  
test1(bar)    #这里如果是test1(bar)就单单是打印bar的内存地址,如果是test1(bar())就是调用了bar(),这样就不符合不能改变调用的的方式这条原则了
  

2.返回值中包含函数名
  不能修改被装饰函数的调用方式 (运行函数没有被修改):
  

import time  
def bar():
  time.sleep(3)
  print ('in the bar')
  

  
def test2(func):
  print (func)
  return func
  
print (test2(bar))     #这里调用了bar()不符合不能改变调用的的方式这条原则。
  

  #执行顺序:调用函数test2>>test2引用了bar >> 将bar传给func >> 打印func >> 将func return给了test2函数 >> 将test2函数通过print给打印出来
  

DSC0009.png

DSC00010.png

  不直接调用bar()也就是不修改bar函数的调用方式;
  t = test2(bar)这个代码本身就会调用test2这个函数(pint也会被调用)>>然后将bar传给了func>>print(func)>> 将func(实际是bar)传给了test2>>然后调用函数t(),因为是将bar传给了test2,所以实际是调用bar()这个函数。
DSC00011.png


嵌套函数
DSC00012.png

  注意这里这个bar()函数相当于局部变量,不能在外面调用
DSC00013.png

DSC00014.png

  函数的嵌套必须是在一个函数体内通过 def 声明另一个函数
DSC00015.png

  这种方式就不是函数嵌套了,这叫函数的调用
DSC00016.png

DSC00017.png

  打印的是l3()函数中的局部变量
DSC00018.png

  图中的l2()被注释掉,那么l2()和l3()就都不会被调用
DSC00019.png

DSC00020.png

  如果在当前作用域中找不到变量,就会到外面一层去找,外面一层没有,就在往外层找。

真正的装饰器
DSC00021.png

  test1 = timer(test1) #这行代码要注意的是,括号中的test1只是将该参数传给func,而这里的timer实际是deco,因为return 时将deco赋值给了timer,所以这里使用test1()实际
DSC00022.png

  可以看到函数test1()没有被做任何的修改,就增加了新的功能,这就是装饰器
DSC00023.png

  每次都要使用这种方式来装饰函数,这样不但不利于理解,而且还麻烦
DSC00024.png

DSC00025.png

  通过使用@timer来装饰一个函数,想要装饰哪个函数就将@timer写到哪个函数的头部即可
  然后test1()这个函数正常调用即可
  用@timer相当于将test1变成timer,而timer又因为return deco,所以这里相当于将test1变成deco,所以在调用函数时test1()=deco()
  @timer除了要改变test1外,还相当于将test1这个内存地址当做参数传给func。
DSC00026.png

DSC00027.png

  test1和test2正常调用函数就行,然后将装饰器写到两个函数的头部即可。


  • 装饰器debug:
    DSC00028.png

      1

DSC00029.png

  2
DSC00030.png

  3:可以看到从def timer(func):直接跳到@timer这个地方,因为该timer这个函数没有被调用,不过timer这个函数的内容已经放入到内存中了
DSC00031.png

  4:可以看到直接跳到def deco()了,所以要搞清楚@timer相当于test1=timer(test1),@timer在这里相当于就执行了timer这个函数,所以第4步就会因为执行了timer函数而到了deco函数这里。
DSC00032.png

  5:由于deco没有被调用,所以不会执行deco函数,不过将deco的内存地址返回给了timer,timer当前相当于deco,print (timer(test1))就相当于打印deco的内存地址
  此时的@timer = test1=timer(test1) ,变量test1相当于deco的内存地址,所以test1()就相当于deco()的调用,也可以说当前的test1()被修改为deco(),所以下面的步骤中可以看到当调用test1()时,就被修改为调用deco()
DSC00033.png

  6:可以看到第6步没有跳到def test1():这里,这是因为在@timer这里已经到过一次test1了(不是调用)
DSC00034.png

  7
DSC00035.png

  8
DSC00036.png

  9:这里看似要调用test1,但实际却不是
DSC00037.png

  10:test1()被@timer给替换成了def deco():
DSC00038.png

DSC00039.png

  11:这里的func()才相当于执行test1这个函数,将鼠标放在func()上面可以显示看到test1函数的内存地址
DSC00040.png

  12:执行test1中的函数体
DSC00041.png

  13
DSC00042.png

  14
DSC00043.png

  15
DSC00044.png

DSC00045.png

  16:然后就到test2()这里了,这里就和test1()的步骤重复了。
DSC00046.png

  注释掉test1的@timer和test1(),在test2函数中加上形参
DSC00047.png

  可以看到执行程序报错,这是因为执行test2()实际是执行deco(),test2函数中有形参,但deco中却没有
DSC00048.png

  在deco()中加上形参,同时调用func()时,也要加上形参
  执行test2()相当于执行deco(),将aubrey传入deco('aubrey'),然后func(arg1)相当于test2('aurbey')
DSC00049.png

  func的arg1只是把deco的arg1(相当于aubrey)拿过来使用,而不是将test2('aubrey')这个参数字符串直接传给func的arg1
DSC00050.png

  将test1注释去掉
DSC00051.png

  可以看到因为test2的原因,将deco和func中传入了形参,但是test1()并没有传入形参,所以会报错
DSC00052.png

  将deco和func中修改为参数组,*args和**kwargs
DSC00053.png

  可以看到这回就不报错了,使用了参数组后参数不但可以为空(适应test1这种情况),同时无论你传入多少参数,参数组都可以满足,属于通用的装饰器了。
DSC00054.png

DSC00055.png

  密码输错
DSC00056.png

  每个页面都需要认证
DSC00057.png

  print
DSC00058.png

DSC00059.png

  home函数中定义返回值,然后打印home函数
DSC00060.png

  可以看到print (home()),值为None
DSC00061.png

  需要将值返回给wrapper并调用func
  return func(*args,**kwargs) #再return func时,调用了func(),暂时不会讲值return给wrapper,而是先进入home函数中,到home函数中house先 print ('welcome to home page'),再return 'from home',此时的return就将'from home'传给了func,而后func又被return给了wrapper,最后根据print (home()),会将from home打印出来
DSC00062.png

  这回可以看到home函数的return值了
DSC00063.png

  想根据指定条件,让home函数在本地认证,让bbs函数在远程认证,这里在@auth后面括号中加上符合的条件(相当于加了一个关键词实参给auth函数),那么auth函数需要重新定义一下形参,但是改变了形参以后,下面的函数就没办法像之前一样调用func()了,所以这里要在多定义一个新嵌套函数outer_wrapper(func)能够让下面的函数wrapper继续调用func(),然后将outer_wrapper返回给auth函数,而最外层的auth函数就可以改变它自己的形参和函数体了。
  想要知道具体执行步骤可以使用debug功能
DSC00064.png

  在之前第7步骤之后应该往下走,可是这里outer_wrapper返回值是被wrapper给返回的,所以第8步到了wrapper这里,然后到了第9步return那里将返回值返回给outer_wrapper,由于下面没有在嵌套函数了,所以就会往下走到第10步
  第9步后,home函数等于wrapper
  第18步相当于调用的就是wrapper()
DSC00065.png

  增加auth_type的if条件来做本地或远程认证
DSC00066.png

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.iyunv.com/thread-547654-1-1.html 上篇帖子: Centos7安装python3.5 下篇帖子: python之事件驱动与异步IO
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表