装饰器概念
装饰器本质是函数,也是通过def定义的。
装饰器的功能是用来装饰其他函数的,也就是为其他函数添加附加功能
装饰器有独特的原则:
不能修改被装饰函数的源代码 (源代码没有被修改)
不能修改被装饰函数的调用方式 (运行函数没有被修改)
总结:就是源代码不能被修改。
装饰器对于被装饰的函数,相当于完全透明的,函数的源代码不能被修改(某些在线的系统如果被修改原代码,不知道会出现什么问题),但又想加些功能,但原函数又不知道自己被新加了功能(没有感知到),这就叫装饰器。
学习装饰器需要掌握的知识:
1.函数即‘变量’
2.高阶函数
3.嵌套函数
高阶函数+嵌套函数=装饰器
函数即‘变量’
def bar():
print ('in the bar')
def foo():
print ('in the foo')
bar()
foo()
函数中调用另一个函数,执行结果!
函数即‘变量’:定义一个函数,相当于将一个函数体赋值给了一个函数名
比如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)
print y的执行结果等于1,也就是y被赋了上面x的值,y = x时,y会从程序的第1行向下去找x来赋值,不会从y下面去找x来赋值,如果y上面存在多个x变量的话,那么就通过离y最近的x来赋值。
可以看到y是不会再其下面去找x变量来赋值的。
函数:
def bar():
print ('in the bar')
def foo():
print ('in the foo')
bar()
foo()
函数与变量相似,只不过函数是在调用函数时的上面如果存在就可以,也就是相关的调用变量或函数在foo()的代码上面存在就会被正常引用,因为foo()相当于函数的一部分。
def foo():
print ('in the foo')
bar()
def bar():
print ('in the bar')
foo()
可以看到函数bar()只要在调用foo()函数之前就可以被正常的引用。
def foo():
print ('in the foo')
bar()
foo()
def bar():
print ('in the bar')
调用函数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()这个函数;
def bar():
print ('in the bar')
def test1(func):
print (func)
func()
#可以通过func()来调用bar()这个函数,因为此时func()被赋值了bar(),此时func()等于bar(),类似x=1,y=x的概念。
test1(bar)
不能修改被装饰函数的源代码 (源代码没有被修改):
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给打印出来
不直接调用bar()也就是不修改bar函数的调用方式;
t = test2(bar)这个代码本身就会调用test2这个函数(pint也会被调用)>>然后将bar传给了func>>print(func)>> 将func(实际是bar)传给了test2>>然后调用函数t(),因为是将bar传给了test2,所以实际是调用bar()这个函数。
嵌套函数
注意这里这个bar()函数相当于局部变量,不能在外面调用
函数的嵌套必须是在一个函数体内通过 def 声明另一个函数
这种方式就不是函数嵌套了,这叫函数的调用
打印的是l3()函数中的局部变量
图中的l2()被注释掉,那么l2()和l3()就都不会被调用
如果在当前作用域中找不到变量,就会到外面一层去找,外面一层没有,就在往外层找。
真正的装饰器
test1 = timer(test1) #这行代码要注意的是,括号中的test1只是将该参数传给func,而这里的timer实际是deco,因为return 时将deco赋值给了timer,所以这里使用test1()实际
可以看到函数test1()没有被做任何的修改,就增加了新的功能,这就是装饰器
每次都要使用这种方式来装饰函数,这样不但不利于理解,而且还麻烦
通过使用@timer来装饰一个函数,想要装饰哪个函数就将@timer写到哪个函数的头部即可
然后test1()这个函数正常调用即可
用@timer相当于将test1变成timer,而timer又因为return deco,所以这里相当于将test1变成deco,所以在调用函数时test1()=deco()
@timer除了要改变test1外,还相当于将test1这个内存地址当做参数传给func。
test1和test2正常调用函数就行,然后将装饰器写到两个函数的头部即可。
- 装饰器debug: 1
2 3:可以看到从def timer(func):直接跳到@timer这个地方,因为该timer这个函数没有被调用,不过timer这个函数的内容已经放入到内存中了
4:可以看到直接跳到def deco()了,所以要搞清楚@timer相当于test1=timer(test1),@timer在这里相当于就执行了timer这个函数,所以第4步就会因为执行了timer函数而到了deco函数这里。
5:由于deco没有被调用,所以不会执行deco函数,不过将deco的内存地址返回给了timer,timer当前相当于deco,print (timer(test1))就相当于打印deco的内存地址
此时的@timer = test1=timer(test1) ,变量test1相当于deco的内存地址,所以test1()就相当于deco()的调用,也可以说当前的test1()被修改为deco(),所以下面的步骤中可以看到当调用test1()时,就被修改为调用deco()
6:可以看到第6步没有跳到def test1():这里,这是因为在@timer这里已经到过一次test1了(不是调用)
7
8
9:这里看似要调用test1,但实际却不是
10:test1()被@timer给替换成了def deco():
11:这里的func()才相当于执行test1这个函数,将鼠标放在func()上面可以显示看到test1函数的内存地址
12:执行test1中的函数体
13
14
15
16:然后就到test2()这里了,这里就和test1()的步骤重复了。
注释掉test1的@timer和test1(),在test2函数中加上形参
可以看到执行程序报错,这是因为执行test2()实际是执行deco(),test2函数中有形参,但deco中却没有
在deco()中加上形参,同时调用func()时,也要加上形参
执行test2()相当于执行deco(),将aubrey传入deco('aubrey'),然后func(arg1)相当于test2('aurbey')
func的arg1只是把deco的arg1(相当于aubrey)拿过来使用,而不是将test2('aubrey')这个参数字符串直接传给func的arg1
将test1注释去掉
可以看到因为test2的原因,将deco和func中传入了形参,但是test1()并没有传入形参,所以会报错
将deco和func中修改为参数组,*args和**kwargs 可以看到这回就不报错了,使用了参数组后参数不但可以为空(适应test1这种情况),同时无论你传入多少参数,参数组都可以满足,属于通用的装饰器了。
密码输错
每个页面都需要认证
home函数中定义返回值,然后打印home函数 可以看到print (home()),值为None
需要将值返回给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打印出来 这回可以看到home函数的return值了
想根据指定条件,让home函数在本地认证,让bbs函数在远程认证,这里在@auth后面括号中加上符合的条件(相当于加了一个关键词实参给auth函数),那么auth函数需要重新定义一下形参,但是改变了形参以后,下面的函数就没办法像之前一样调用func()了,所以这里要在多定义一个新嵌套函数outer_wrapper(func)能够让下面的函数wrapper继续调用func(),然后将outer_wrapper返回给auth函数,而最外层的auth函数就可以改变它自己的形参和函数体了。
想要知道具体执行步骤可以使用debug功能
在之前第7步骤之后应该往下走,可是这里outer_wrapper返回值是被wrapper给返回的,所以第8步到了wrapper这里,然后到了第9步return那里将返回值返回给outer_wrapper,由于下面没有在嵌套函数了,所以就会往下走到第10步
第9步后,home函数等于wrapper
第18步相当于调用的就是wrapper()
增加auth_type的if条件来做本地或远程认证