装饰器--初识
函数调用的顺序
和其他语言类似。python函数在未经声明之前,不允许对其引用和调用。
1 1、未声明之前,调用或者引用会报错。 2 def t1(): 3 print('This is t1...') 4 t2() 5 6 t1() 7 8 def t2(): 9 print('This is t2...') 10 11 12 显示结果: 13 This is t1... 14 Traceback (most recent call last): 15 File "D:/软件/pychar/data/s13/practise1.py", line 37, in <module> 16 t1() 17 File "D:/软件/pychar/data/s13/practise1.py", line 35, in t1 18 t2() 19 NameError: name 't2' is not defined 20 21 22 23 2、正确的调用顺序:(先声明函数,在引用或者调用) 24 def t1(): 25 print('This is t1...') 26 t2() 27 28 def t2(): 29 print('This is t2...') 30 31 t1() 32 33 显示结果: 34 This is t1... 35 This is t2...
函数的功能与作用
需要添加一个打印日志功能。
在没学函数的时候,只能用print显示打印,例如:
1 def t1(): 2 pass 3 print('logging') 4 5 def t2(): 6 pass 7 print('logging')
学了函数以后可以定义一个函数,例如:
def logger(): print('logging') def t1(): pass logger() #直接调用函数 def t2(): pass logger()
装饰器的功能与特点
对已经上线的代码需要添加功能:
问题:
1、代码行数很多,查找很麻烦,而且要挨个找出,把新功能函数加进去。
2、代码已经上线,对源代码修改可能会导致未知故障发生。(新增功能是不能修改源代码的)
3、代码已经上线,不光源代码不能随便修改,就连函数调用方式也不能随意修改。
解决方法:使用装饰器进行代码修饰。
装饰器的概述
一、定义:
装饰器本身就是一个函数,遵循函数的声明,引用,调用,作用域等相关规则。
二、作用:
装饰器:目的是(装饰其他函数)就是为其他函数添加附加功能。
三、用途:
装饰器:最常见的用途主要体现在用户登录与权限认证。
四、原则:
1、不能修改被装饰的函数的源代码。
2、不能修改被装饰的函数的调用方式。
3、装饰器对被装饰的函数时完全透明的。(源代码感知不到装饰器函数的存在,但装饰器函数确实影响着源代码)
五、调用方法:
@+装饰器的函数名;
一个原函数体可以调用多个装饰器,执行顺序如下:
装饰器的知识储备
1、函数即"变量"
2、高阶函数。
3、嵌套函数。
最终:高阶函数+嵌套函数 =》装饰器函数
知识点一:函数即“变量”:
定义一个x或者y是装在内存中声明一个内存块,并通通过存地址指定给x或者y,当调用x或者y时可以加载在内存块中进行引用或调用。
定义一个test的函数的原理同上。
定义一个无名子的函数体原理也同上。
注意:
解释器会先回收无名子的函数体所占的内存块(即lambda的函数模块)
del 并不是删除内存块,而是直接删除内存定义的名字,通过解析器在清除内存块。
知识点二、高阶函数:
满足下面两个条件之一就是高阶函数
a:把一个函数名当做实参传给另外一个函数,即某一函数当做参数传入另一个函数中(在不修改被装饰函数源代码的情况下添加功能)
b:返回值中包含函数名,即函数的返回值包含n个函数,n>0(不修改函数的调用方式)
练习a:
1 #!/usr/bin/env python 2 # -*- coding:utf8 -*- 3 # Author:Dong Ye 4 5 import time 6 7 #定义一个源代码。要求不允许改变源代码和调用方式的情况下进行装饰: 8 def bar(): #定义个变量,模拟线上函数体 9 time.sleep(3) 10 print("in the bar") 11 12 13 #用高阶函数装饰bar代码 14 def test1(func): #定义一个形参,用于接收调用test1函数的实参,如果传过来的是函数名,则该函数已经被应用。 15 start_time = time.time() 16 func() #调用bar函数,类似于x=1,y=x 显示y。而func = bar,直接调用func 17 stop_time = time.time() 18 print("the func run time is %s (non bar run time)" %(stop_time-start_time)) 19 20 test1(bar) #调用仿装饰器传实参给test1(模拟线上环境的函数)
重点:结合装饰器的重要的两个特点,做个练习1:
1、不能修改被装饰的函数的源代码。
2、不能修改被装饰的函数的调用方式。
问题:
1、装饰器并没有修改源代码bar
2、由于test1(bar)改变了调用函数的方式,因此不符合装饰器的条件。
注释:
test1(bar)与test1(bar())的区别:
test1(bar):是把内存地址通过实参方式传给test1函数作为形参。
test1(bar()):是把内存中的函数体通过实参方式传给test1函数作为形参。
练习b:返回值中包含函数名。(不修改函数的调用方式)
1 #!/usr/bin/env python 2 # -*- coding:utf8 -*- 3 # Author:Dong Ye 4 ''' 5 import time 6 7 #定义一个源代码。要求不允许改变源代码和调用方式的情况下进行装饰: 8 def bar(): #定义个变量,模拟线上函数体 9 time.sleep(3) 10 print("in the bar") 11 12 13 #用高阶函数装饰bar代码 14 def test1(func): #定义一个形参,用于接收调用test1函数的实参,如果传过来的是函数名,则该函数已经被应用。 15 start_time = time.time() 16 func() #调用bar函数,类似于x=1,y=x 显示y。而func = bar,直接调用func 17 stop_time = time.time() 18 print("the func run time is %s (non bar run time)" %(stop_time-start_time)) 19 20 test1(bar) #调用仿装饰器传实参给test1(模拟线上环境的函数) 21 22 ''' 23 24 import time 25 26 #定义模拟线上代码 27 def bar(): 28 time.sleep(3) 29 print('in the bar') 30 31 32 #用高阶函数做装饰器 33 def test2(func): 34 print(func) #打印bar的内存地址 35 return func #返回test2的运行结果地址 36 37 #调用方式一:展示返回值 38 print(test2(bar)) 39 40 #调用方式二:获取返回值 41 t = test2(bar) 42 print(t) 43 #注释: 44 #首先、test2函数把bar传到test2函数里,然后打印bar函数的内存地址。 45 #其次、将bar的内存地址return返回给t。 46 #最后、打印t接收的bar地址。 47 48 #调用方式三:变量名覆盖 49 bar = test2(bar) #将装饰器返回bar()的内存地址,覆盖掉原函数bar 50 bar() #原函数调用方式 51 52 #注释: 53 #由于bar在源代码中已经定义了。 54 #为了不影响源代码函数的调用方式,只提取了bar一开始传实参的内存地址 55 #当装饰器函数的功能执行完毕后,在将bar的内存地址返回,重新赋值给变量 56 #最后、从原函数的被调用的角度来看并没有改变bar()的调用方式, 57 # 而是在原基础上利用装饰器返回的bar的内存值进行了第二次赋值。 58 #这样装饰器功能不仅能够与源代码完美结合,又能满足不修改源代码,不修改调用函数的方法。
知识点三、嵌套函数:
在一个函数体内创建另外一个函数,这种函数就叫内嵌函数(基于python支持静态嵌套域)
1 #嵌套函数: 2 #概念:在一个函数的函数体内,用def去声明一个子函数 3 #作用:高阶函数+嵌套函数=装饰器 4 def foo(): 5 print('in the foo') 6 def bar(): 7 print('in the bar') 8 9 #在调用bar函数的时候,就跟调用局部变量一样,只能在内部调用 10 bar() 11 foo() 12 13 14 15 #局部作用域和全局作用域的访问顺序: 16 x = 0 17 def grandpa(): 18 #x = 1 19 def dad(): #相当于定义了一个变量,如果不调用就相当于什么也没做。 20 x = 2 21 def son(): 22 x = 3 23 print(x) #这个值应该是3,因为只定义在son这个函数体里 24 25 son() #如果不调用就不会显示3,因为嵌套函数时一层一层的找。 26 dad() #上面的定义如果不在这里调用,就等于什么也没做。 27 grandpa()