#Python函数进阶-装饰器
一、装饰器定义
1、装饰器:本质是函数。器,代表函数的意思。
2、功能:用来装饰其他函数,就是为其他的函数添加附件功能。且被装饰的函数感受不到装饰器的存在。
理解装饰器:我们的函数好比内裤,作用是遮羞。但在一些特定的环境,内裤明显满足不了我们的需求,冬天它没法为我们防风御寒。所以有了长裤,装饰器就像长裤,在不影响内裤作用的前提下,给我们的身子提供了保暖的功效。
二、装饰器原则
1、不能修改被装饰函数的源代码
2、不能修改被装饰函数的调用方式
装饰器为什么会有这两个原则呢?因为如果你写的这个程序在生产环境下已经运行了,如果修改别人的源代码或者修改别人的调用方式,那么出了问题,后果可想而知,所以我们要牢记上面两个原则。
三、实现装饰器的知识储备
1、函数即"变量"
定义变量:
如:定义变量:x=1,会在内存中找块内存空间把“1”存进去,把“1”的内存地址给x
定义变量:函数 test = '函数体',会在内存中找块内存空间把“函数体”存进去,把“函数体”的内存地址给test
#变量 x = 1 #函数 def test(): pass # 就相当于把函数体赋值给test变量(函数名) test = '函数体' # 函数体就是一堆字符串而已 # 只不过函数调用要加上小括号调用 test()
以上一个变量一个函数在内存中的表现形式如下图:
在python解释器中,有一个概念叫做引用计数,那什么叫引用计数呢?比如:定义x=1,之后又定义了y=1或y=x,实际上又把内存空间“1”的内存地址赋值给y。这里x代表一次引用,y代表一次引用。加起来两次引用。 python什么时候会把“1”这个内存空间清空呢?当x这个变量没有了,y这个变量也没有了,便会把“1”这个内存空间清掉。这个也是python的内存回收机制,就是靠python解释器来回收内存的。
del x #删除的只是变量名,内存空间“1”还没有删掉,要靠python解释器去回收。python解释器会定期刷新,如果发现内存空间“1”没有被任何变量引用,就回收该内存地址。
注意一点:匿名函数没有函数名(也就没有变量名),所以没有被引用,被会python解释器立马回收掉内存地址。
所以匿名函数可以将函数体赋值给变量名,来引用。
calc = lambda x:x*x print(calc(4))
函数在内存中的表现形式:
我们先通过三个例子来解释一下:
①bar函数在foo函数之后定义
#bar函数在foo函数之后定义 def foo(): print("in the foo") bar() def bar(): print("in the bar") foo() #输出 in the foo in the bar
②bar函数是在foo函数之前定义
# bar函数是在foo函数之前定义 def bar(): print("in the bar") def foo(): print("in the foo") bar() foo() #输出 in the foo in the bar
显然,两种写法效果是一样的,那我们来看看第三种情况。
③bar函数在foo函数调用之后声明
# bar函数在foo函数调用之后声明 def foo(): print("in the foo") bar() foo() def bar(): print("in the bar") #输出 Traceback (most recent call last): in the foo File "D:/PycharmProjects/pyhomework/day4/装饰器/函数即变量.py", line 31, in <module> foo() File "D:/PycharmProjects/pyhomework/day4/装饰器/函数即变量.py", line 29, in foo bar() NameError: name 'bar' is not defined #bar函数没有定义
因为在执行foo函数时,当调用bar函数时,bar函数还没有定义,所以报错。
2、高阶函数
实现高阶函数有两个条件:
(1)把一个函数名当做参数(形参和实参)传给另外一个函数
(2)返回值中包含函数名
1)把一个函数名当做形实传给另外一个函数
作用:在不修改被装饰函数源代码的情况下为其添加功能
import time def func1(): print("in the func1") time.sleep(1) def test1(func): start_time = time.time() func() stop_time = time.time() print("the func run time is %s" %(stop_time-start_time)) test1(func1) #输出 in the func1 the func run time is 1.0006296634674072
这里:没有修改func1的源代码,但是调用方式改变了。之前是func1(),现在是test1(func1)
2)返回值中包括函数名
作用:不修改函数调用方式
import time def func2(): time.sleep(1) print("in the func2") def test2(func): print(func) return(func) print(test2(func2)) #输出 <function func2 at 0x00DAE390> <function func2 at 0x00DAE390>
把函数内存地址都打印出来了,看到这么多内存地址,有什么想法?
加上小括号就能运行。
上面代码“test2(func2())”和“test2(func2)”有什么区别?加上小括号是函数返回结果,不加是函数内存地址。所以加上小括号就不符合高阶函数定义了。
既然以后有了函数的内存地址,是不是可以赋值给其他变量?下面
import time def func2(): print("in the func2") time.sleep(1) def test2(func): print(func) return(func) t = test2(func2) print(t) t() #输出 <function func2 at 0x01DDE390> <function func2 at 0x01DDE390> in the func2
好像还没什么用,怎么让他有用呢?
把test2(func2)赋值给func2
import time def func2(): print("in the func2") time.sleep(1) def test2(func): print(func) return(func) func2 = (test2(func2)) func2() #输出 <function func2 at 0x00D6E390> in the func2
这就是高阶函数的第2个好处:返回值中包含函数名(不修改函数的调用方式)
3、嵌套函数
嵌套函数:在一个函数体内,用def去声明一个函数
def foo(): print("in the foo") def bar(): print("in the bar") bar() foo() #输出 in the foo in the bar
看一下下面的代码是不是嵌套:
def foo(): print("in the foo") def bar(): foo() bar() #输出 in the foo
总结:
(1)函数里面可以定义函数
(2)要想函数被执行,必须要先调用
(3)由内向外一层层的找
(4)代码定义完成,作用域就生成
(5)注意函数嵌套和函数调用区别
4、闭包
闭包(closure)是函数式编程的重要的语法结构。闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。
如果在一个内部函数里,对在外部函数内(但不是在全局作用域)的变量、参数进行引用,那么内部函数就被认为是闭包(closure)。
定义在外部函数内但由内部函数引用或者使用的变量称为自由变量。
有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。
总结一下,创建一个闭包必须满足以下几点:
(1)必须有一个内部函数
(2)内部函数必须引用外部函数中的变量或参数
(3)外部函数的返回值必须是内部函数
def outer(): name = 'alex' def inner(): # 条件1:inner 就是内部函数 print("在inner里打印外层函数的变量",name) # 条件2:引用外部函数的一个变量 return inner # 条件3:外部函数的返回值是内部函数 f = outer() f()
闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域。
装饰器就是一种闭包的应用,只不过其传递的参数是函数名
四、实现装饰器
最终: 高阶函数+嵌套函数+闭包 => 装饰器
前面铺垫了那么多,现在开讲正题:装饰器
先用高阶函数实现给函数不修改源代码的情况下添加功能
import time def deco(func): start_time = time.time() func() stop_time = time.time() print("the func tun time is %s" %(stop_time-start_time)) def test1(): time.sleep(1) print("in the test1") def test2(): time.sleep(1) print("in the test2") deco(test1) deco(test2) #输出 in the test1 the func tun time is 1.0006277561187744 in the test2 the func tun time is 1.0006263256072998
按照上面说的,如何实现不改变调用方式?直接“test1 = deco(test1)”和“test2 = deco(test2)”吗?
别忘记了,第二种方式,高阶函数要加上return,如下
import time def deco(func): start_time = time.time() return func stop_time = time.time() print("the func tun time is %s" %(stop_time-start_time)) def test1(): time.sleep(1) print("in the test1") def test2(): time.sleep(1) print("in the test2") test1 = deco(test1) test2 = deco(test2) test1() test2() #输出 in the test1 in the test2
虽然没有修改源代码和调用方式,但是函数加上return,函数就结束了,然并卵。怎么实现呢?
前面一直在用高阶函数,还没有用嵌套函数,加上嵌套函数能不能实现呢?看一下
import time def timer(func): # timer(test1) func=test1 def deco(): start_time = time.time() func() stop_time = time.time() print("the func tun time is %s" %(stop_time-start_time)) return deco # 返回deco的内存地址 def test1(): time.sleep(1) print("in the test1") def test2(): time.sleep(1) print("in the test2") print(timer(test1)) # 可见:返回deco的内存地址 test1 = timer(test1) test1() timer(test2)() #输出 <function timer.<locals>.deco at 0x0219E348> in the test1 the func tun time is 1.00062894821167 in the test2 the func tun time is 1.0006279945373535
到此,完成实现了装饰器的功能。但是还是有点麻烦,如何能不要“test1 = timer(test1)”,
python解释器提供了语法糖“@”符合,给哪个函数新增功能,就加在哪个函数头部
import time def timer(func): # timer(test1) func=test1 def deco(): # 有内部函数 start_time = time.time() func() # 引用外部函数的参数 stop_time = time.time() print("the func tun time is %s" %(stop_time-start_time)) return deco # 返回值是内部函数 @timer def test1(): time.sleep(1) print("in the test1") @timer def test2(): time.sleep(1) print("in the test2") test1() test2() #输出 in the test1 the func tun time is 1.0006308555603027 in the test2 the func tun time is 1.0006275177001953
五、装饰有参数的函数
前面实现了装饰器的功能,但是如果函数有参数,能不能也能运行呢
import time def timer(func): # timer(test1) func=test1 def deco(): start_time = time.time() func() stop_time = time.time() print("the func tun time is %s" %(stop_time-start_time)) return deco # 返回deco的内存地址 @timer def test1(): time.sleep(1) print("in the test1") @timer def test2(name): time.sleep(1) print("in the test2",name) test1() test2()
报错:丢失参数
TypeError: test2() missing 1 required positional argument: 'name'
@timer 相当于 test2=timer(test2) =deco
test2() 相当于运行deco(),所以没指定参数,报错。
如何传参数呢?为了适应各种不同参数的函数,我们传入非固定参数。
import time def timer(func): # timer(test1) func=test1 def deco(*args,**kwargs): # 非固定参数 start_time = time.time() func(*args,**kwargs) # 非固定参数 stop_time = time.time() print("the func tun time is %s" %(stop_time-start_time)) return deco # 返回deco的内存地址 @timer def test1(): time.sleep(1) print("in the test1") @timer def test2(name): time.sleep(1) print("in the test2",name) test1() test2("fgf") #输出 in the test1 the func tun time is 1.0004048347473145 in the test2 fgf the func tun time is 1.0003695487976074
六、终极装饰器
注意,上面的例子中还没有涉及返回值,看下面的例子可以体会一下
假设:公司网站需要验证登录,有不同的验证方式:本地认证、LDAP认证等
#/usr/bin/env python # -*- coding: UTF-8 -*- import time user,passwd = 'fgf','abc123' def auth(auth_type): print("auth func:",auth_type) def outer_wrapper(func): def wrapper(*args, **kwargs): print("wrapper func args:", *args, **kwargs) if auth_type == "local": username = input("Username:").strip() password = input("Password:").strip() if user == username and passwd == password: print("\033[32;1mUser has passed authentication\033[0m") res = func(*args, **kwargs) # from home print("---after authenticaion ") return res else: exit("\033[31;1mInvalid username or password\033[0m") elif auth_type == "ldap": print("搞毛线ldap,不会。。。。") return wrapper return outer_wrapper def index(): print("welcome to index page") @auth(auth_type="local") # home = wrapper() def home(): print("welcome to home page") return "from home" @auth(auth_type="ldap") def bbs(): print("welcome to bbs page") index() print(home()) #wrapper() bbs()
从上面的例子可以看出,执行步骤:
(1)outer_wrapper = auth(auth_type="local")
(2)home = outer_wrapper(home)
(3)home()
所以这个函数的作用分别是:
(1)auth(auth_type) 传递装饰器的参数
(2)outer_wrapper(func) 把函数当做实参传递进来
(3)wrapper(*args,**kwargs) 真正执行装饰的函数
七、装饰器武功秘籍
- 简单装饰器
def outer_wrapper(func): # 参数是一个函数名 """ 这是一个装饰器,实质是一个高阶函数+嵌套函数+闭包 :param func: :return: """ def wrapper(): # 有内部函数 func() # 引用外部函数的参数 print("我增加了一个功能") print("且没有修改原来的函数代码和调用参数") return wrapper # 返回值是内部函数名 @outer_wrapper def f1(): print("我有一个基本功能") # f1=outer(f1) #这句相当于直接在被装饰的函数上加语法糖“@outer” f1()
- 装饰有参数的函数
def outer_wrapper(func): def wrapper(*args,**kwargs): func(*args,**kwargs) print("增加1个功能") return wrapper @outer_wrapper def f2(name): print("我有1个带参数%s的功能" %(name)) f2("666")
- 终极装饰器(有返回值,有参数)
def choice(type): def outer_wrapper(func): def wrapper(*args,**kwargs): res = func(*args,**kwargs) print("增加1个功能,类型是%s" %(type)) return res return wrapper return outer_wrapper @choice(type='1') def f2(name): print("我有1个带参数%s的功能" %(name)) return "我有返回值" @choice(type='2') def f3(name): print("我有1个带参数%s的功能" %(name)) print(f2("666")) f3("888")