装饰器概念

装饰器本质是函数,也是通过def定义的。

装饰器的功能是用来装饰其他函数的,也就是为其他函数添加附加功能

装饰器有独特的原则:

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

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

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

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

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

1.函数即‘变量’

2.高阶函数

3.嵌套函数

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

函数即‘变量’

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

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

foo()

image_1c0ajsc3r1t4ivku1npclhb1f09.png-1.6kB 函数中调用另一个函数,执行结果!

函数即‘变量’:定义一个函数,相当于将一个函数体赋值给了一个函数名

比如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)

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

image_1c0ajvki5ft51odfap14lj1m813.png-3.9kB

image_1c0ajvqjdlq91cnk1uq884p7701g.png-10.1kB 可以看到y是不会再其下面去找x变量来赋值的。

函数:

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

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

foo()

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

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


foo()

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

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

foo()

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

image_1c0ak3r3a1q1qc1p13j11r014jc2n.png-16.5kB 调用函数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()这个函数;  

image_1c0ak92gu147ffiaes98g17mh34.png-3.3kB

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

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

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

test1(bar)

image_1c0ak9ojhmk2ucv6t6pm9rj73h.png-3.8kB

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

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给打印出来

image_1c0akc8361b5s530c667lf5kt3u.png-3.4kB

image_1c0akchaf1s68169jjkl1skpru94b.png-17.3kB 不直接调用bar()也就是不修改bar函数的调用方式;

t = test2(bar)这个代码本身就会调用test2这个函数(pint也会被调用)>>然后将bar传给了func>>print(func)>> 将func(实际是bar)传给了test2>>然后调用函数t(),因为是将bar传给了test2,所以实际是调用bar()这个函数。 image_1c0akd6etdj8bnm105bnmi150f4o.png-3.5kB

嵌套函数

image_1c0akdlrq4vd15oa19u61aof1i4p55.png-8.9kB 注意这里这个bar()函数相当于局部变量,不能在外面调用 image_1c0akekmk1uin1gk61h70l1i3t65v.png-9.3kB

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

image_1c0akgek85bpqhlr8r19pvus38m.png-5kB 这种方式就不是函数嵌套了,这叫函数的调用

image_1c0akgoek1bq5ksiir1hoq1ff893.png-10kB

image_1c0akgu2c11851ovn18lq1l3166e9g.png-0.4kB 打印的是l3()函数中的局部变量

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

image_1c0akhnen4711oea12uu2s2192maa.png-10.4kB

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

真正的装饰器

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

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

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

image_1c0akk2c314dn1u3f121l13scogfcb.png-44.8kB

image_1c0akk8hq1fajou5dpairvedvco.png-3.4kB 通过使用@timer来装饰一个函数,想要装饰哪个函数就将@timer写到哪个函数的头部即可

然后test1()这个函数正常调用即可

用@timer相当于将test1变成timer,而timer又因为return deco,所以这里相当于将test1变成deco,所以在调用函数时test1()=deco()

@timer除了要改变test1外,还相当于将test1这个内存地址当做参数传给func。

image_1c0akkonccvoatt1jffa981luod5.png-47.7kB

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


  • 装饰器debug: image_1c0akmmq7v6r132kpjg4991cecdv.png-31.9kB 1

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

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

image_1c0akpn7i1hp01a2s1digp5h1n1lh0.png-45.6kB 5:由于deco没有被调用,所以不会执行deco函数,不过将deco的内存地址返回给了timer,timer当前相当于deco,print (timer(test1))就相当于打印deco的内存地址

此时的@timer = test1=timer(test1) ,变量test1相当于deco的内存地址,所以test1()就相当于deco()的调用,也可以说当前的test1()被修改为deco(),所以下面的步骤中可以看到当调用test1()时,就被修改为调用deco()

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

image_1c0akqib91v99mgp66ibo61nr7hq.png-47.2kB 7

image_1c0akrdfu4ei5gi1cog17b0442i7.png-47kB 8

image_1c0akrm033hp1hom14qs1tlqjn4ik.png-47.3kB 9:这里看似要调用test1,但实际却不是

image_1c0aks5u73jb1c2t102qecvnt1j1.png-46.9kB 10:test1()被@timer给替换成了def deco():

image_1c0aksh4ku0cg681rb41c9n1dqvje.png-48.7kB

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

image_1c0akt1ml1ib21j8j1jfutft6b2k8.png-47.6kB 12:执行test1中的函数体

image_1c0aktgd2pef1mla1krrrgl2ekl.png-47.5kB 13

image_1c0aktrb61evs88c1bmvran1tbtl2.png-50.2kB 14

image_1c0aku9tue04kjkute5f13lllf.png-52kB 15

image_1c0akui7brvn1bl61l4e17rtdkuls.png-3.5kB

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


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

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

image_1c0al17od1u5cj3j1li1cb41ivang.png-51.9kB 在deco()中加上形参,同时调用func()时,也要加上形参

执行test2()相当于执行deco(),将aubrey传入deco('aubrey'),然后func(arg1)相当于test2('aurbey')

image_1c0al1jvvj8hgkc1tar19ul1pbjnt.png-4kB

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

image_1c0al3i601p910pqg74ngiqcloa.png-51.9kB 将test1注释去掉

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

image_1c0al4bhtmgb1u0615e72duj6pp4.png-53.4kB 将deco和func中修改为参数组,*args和**kwargs image_1c0al4js218gh1jgp1lu11ugf1hucph.png-6.9kB 可以看到这回就不报错了,使用了参数组后参数不但可以为空(适应test1这种情况),同时无论你传入多少参数,参数组都可以满足,属于通用的装饰器了。


image_1c0al57k49ijho51r5gbcm8g0pu.png-69.3kB

image_1c0al5dsl32d118918q01igo1md6qb.png-6.3kB 密码输错

image_1c0al5mhd1l6l2djlroq1qraqo.png-16.7kB 每个页面都需要认证

image_1c0al61cgq6e1lq51g781lan1ajkr5.png-63.6kB print image_1c0al6ptf17eu14561v9f4jbsd1ri.png-9.2kB

image_1c0al8mu8uav13m2115v1ce61asorv.png-68.8kB home函数中定义返回值,然后打印home函数 image_1c0al8vuigpngmc13h612lj1u3isc.png-9.6kB 可以看到print (home()),值为None

image_1c0al9g60fjuaf1ejr1p1o1measp.png-69.7kB 需要将值返回给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打印出来 image_1c0alab6a15bm1db51vnh1qf21ltdt6.png-9.6kB 这回可以看到home函数的return值了

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

想要知道具体执行步骤可以使用debug功能

image_1c0alc4su1v0hplv19bashte15v0.png-121.7kB 在之前第7步骤之后应该往下走,可是这里outer_wrapper返回值是被wrapper给返回的,所以第8步到了wrapper这里,然后到了第9步return那里将返回值返回给outer_wrapper,由于下面没有在嵌套函数了,所以就会往下走到第10步

第9步后,home函数等于wrapper

第18步相当于调用的就是wrapper()

image_1c0alccj21e4p1acd1oecglr33pvd.png-94.4kB 增加auth_type的if条件来做本地或远程认证 image_1c0alcp6m1sm3a4pg2o9lnmmvq.png-11.9kB