Python的装饰器
概要
- 什么是装饰器
- 怎么使用装饰器
- 使用场景
- 如何给装饰器传递参数
1. 什么是装饰器
能够对其他函数的功能进行增强。也就是函数代码功能重用。它是一个设计模式。需要注意的是:
- 装饰器本身是一个函数
- 增强被装饰函数的功能,同时被增强的函数还不能感受到自己被增强了或者说被修了
- 装饰器需要接受一个函数对象作为参数以对其进行包装
- 不会改变被调用函数的调用方式
2. 怎么使用装饰器
要理解装饰器你需要理解如下内容:
- 函数即变量 def 定义一个函数就等于把函数体赋值给了一个变量(函数名)
- 高阶函数 满足两个条件 把一个函数名当做形参传入另外一个函数、返回值中包含函数名
- 嵌套函数 函数中嵌套函数
装饰器=高阶函数+函数嵌套
2.1 函数嵌套
#!/usr/bin/env python # Author: rex.cheny # E-mail: rex.cheny@outlook.com def fun3(): print("in the fun3") def fun4(): print("in the fun4") fun4() if __name__ == '__main__': fun3()
执行结果
在fun3中定义一个fun4函数,执行fun3,同时也会执行fun4。这个比较好理解就是函数中嵌套一个函数,对于嵌套的函数(这里是fun4)你可以在你调用的函数(这里是fun3)中执行,也可以把这个fun4返回。
2.2 高阶函数
#!/usr/bin/env python # Author: rex.cheny # E-mail: rex.cheny@outlook.com def fun1(): print("Hello fun1") def fun2(fun): print("World fun2") # 这里是执行的传入的函数本身 fun() # 返回回去 return fun # 执行fun2同时把fun1当做参数传递进去 fun2(fun1)
上面满足高阶函数的2个条件。但是如果我只是想给fun1增加功能,那么我这样执行显然成功了,可是我改变了调用方式,也就是说我必须执行fun2(fun1)才行,而我程序中很多地方写的都是fun1(),那如果我这样改起来自己麻烦同时如果别人调我的fun1(),那别人也要改。如何不改变调用方式呢?通过下面的方式调用,不过这不再是单纯的给fun1增加功能,因为最后相当于重新定义了fun1。
# 如何不改变调用方式也增加新功能呢?就这样。这里相当于把之前的fun1重新定义了 fun1 = fun2(fun1)
2.3 装饰器如何使用
#!/usr/bin/env python # Author: rex.cheny # E-mail: rex.cheny@outlook.com import time def timer(func): def wapper(*args, **kwargs): start_time = time.time() func() stop_time = time.time() print("耗时:", stop_time - start_time) return wapper @timer def run(): print("To do job.") time.sleep(2) print("Job is done.") run()
我这里是给run函数增加一个功能计算该函数的运行时间。上面就是装饰器的用法。增加了功能、没有改变原来的调用方式,timer函数随处可以引用。如果上面的功能通过高阶函数如何实现呢?你看下面的代码几乎完全相同,只是run()函数上面去掉了@timer,以及后面的执行语句前面有加了一个语句,正像上面所说执行完 run = timer(run) 其实就等于重新定了run()。
#!/usr/bin/env python # Author: rex.cheny # E-mail: rex.cheny@outlook.com import time def timer(func): def wapper(*args, **kwargs): start_time = time.time() func() stop_time = time.time() print("耗时:", stop_time - start_time) return wapper def run(): print("To do job.") time.sleep(2) print("Job is done.") run = timer(run) run()
加上 @timer 之后,运行 run()就等于运行 timer函数中的wapper。因为:如果不加 @timer 你可以这么用,其实这么用的效果和加 @timer 然后调用run()是一样的。
加上 @timer 之后,执行过程是什么样的呢?
- 从上到下执行,遇到 def timer(func) 函数解释器知道是定义了一个函数,因为此时没有调用所以解释器继续向下执行
- 遇到@timer,解释器执行 def timer(func) 并把 run() 函数传递给 def timer(func) 中的 func,然后发现定义了一个函数 def wapper,它此时也不执行,继续向下,遇到return wapper 则返回wapper函数
- 此时装饰完毕其实也就是对run()函数进行了重新赋值,继续向下,遇到run()这条语句,也就是我们直接调用,这时候它会去执行之前返回的wapper函数,此时run()就是wapper()函数 理解为 run = timer(run)
- 执行wapper函数的第一条语句 start_time = time.time()
- 执行wapper函数的第二条语句 func() 而这时候的func()就是之前传递进来的run()函数,此时执行run()函数的语句,直到执行完成返回
- 执行执行wapper函数第三条语句 stop_time = time.time()
- 执行执行wapper函数第四条语句 print("耗时:", stop_time - start_time)
2.4 装饰器如何传递参数
我们可能注意到 def wapper(*args, **kwargs) 其实这里就可以传递参数。
#!/usr/bin/env python # -*- coding: utf-8 -*- # Author: rex.cheny # E-mail: rex.cheny@outlook.com def printName(func): def wapper(*args, **kwargs): func(**kwargs) return wapper @printName def run(name): print("姓名为:", name) def main(): run(name="chen") if __name__ == '__main__': main()
可能有些人对 **kwargs 有些迷惑,其实它就是一个字典形式,你传进来的参数必须是 KEY=VALUE形式,就行我在main()函数中的那样 run(name="chen"),如果还不明白我换一种写法如下图:
2.5 类中如何使用装饰器
#!/usr/bin/env python # -*- coding: utf-8 -*- """ """ import sys import time def timer(tagName): import time def wapper(func): def aa(self, *args, **kwargs): start = time.time() func(self) end = time.time() consume = end - start if consume > 60: min, sec = divmod(consume, 60) print(" - %s 执行耗时:%s 分 %s 秒。" % (tagName, str(min), str(sec))) else: print(" - %s 执行耗时:%s 秒。" % (tagName, str(consume))) return aa return wapper class AA: def __init__(self): pass @timer(tagName="runTask方法") def runTask(self): time.sleep(2) if __name__ == "__main__": try: aa = AA() aa.runTask() except Exception as err: print(err) finally: sys.exit()
3. 实例
我这里模拟一个场景就是网站登录,我们需要给所有页面增加一个验证功能。
#!/usr/bin/env python # Author: rex.cheny # E-mail: rex.cheny@outlook.com """ 模拟网站登录,也就是给某些页面加验证功能 """ users = {"Tom": "123"} # 验证功能 def auth(func): def wrapper(*args, **kwargs): username = input("Input your username: ") password = input("Input your password: ") if username.strip() in users and password.strip() == users[username]: func(username=username) # 如果你需要接收被装饰函数的返回值就要 # res = func(username=username) else: exit("Invalid username or password.") return wrapper # 下面是模拟两个页面 def indexPage(): print("Welcome to Index.") # 给home页面加验证 @auth def homePage(**kwargs): print("Welcome: ", kwargs["username"], "to Home.") return "You are Home." def main(): indexPage() homePage() if __name__ == '__main__': main()
执行结果
现在的需求变了,我又增加了一个页面,同时这个页面需要另外一种验证方式,怎么办?最笨的方法是再写一个装饰器用于其他验证方式,有没有更好的办法呢?就是设置装饰器参数。
#!/usr/bin/env python # Author: rex.cheny # E-mail: rex.cheny@outlook.com """ 模拟网站登录,也就是给某些页面加验证功能 """ users = {"Tom": "123"} def authG_two(auth_type="local"): """ 这个装饰器带了参数,所以它就不能写fun,而必须是明确的参数。而且这里和之前比又多了一层, 因为最外层不是传递的函数进来,而是参数。这个是如何执行的呢? @authG_two(auth_type="local") def fun1(): pass fun1 = authG_two(auth_type="local") 然后返回 wrapper,这时候 fun1 = wrapper(fun1),然后在进入到 authG_two() 执行 wrapper()方法 根据auth_type返回 对应的函数,如果是local 最终则是 fun1 = localAuth()并且 localAuth()里的func函数就是之前传递进去的func函数。 """ def wrapper(func): # 本地验证方式 def localAuth(*args, **kwargs): username = input("Input your username: ") password = input("Input your password: ") if username.strip() in users and password.strip() == users[username]: print("Local authentication succeed.") func(username=username) # 如果你需要接收被装饰函数的返回值就要 # res = func(username=username) else: exit("Invalid username or password.") # LDAP验证方式 def ldapAuth(*args, **kwargs): username = input("Input your username: ") password = input("Input your password: ") func(username=username) print("LDAP authentication succeed.") if auth_type == "local": return localAuth else: return ldapAuth return wrapper """ 下面三个模仿三个板块的主页 """ def indexPage(): print("Welcome to Index.") @authG_two() def homePage(**kwargs): """ 这里使用新的装饰器,它后面带括号,不设置参数表示使用默认参数设置。 """ print("Welcome: ", kwargs["username"], "to Home.") return "You are Home." # 新增BBS页面 @authG_two(auth_type="ldap") def bbsPage(**kwargs): """ 这里使用新的装饰器,它后面带括号,这就说明那个装饰器需要带参数,验证方式为LDAP """ print("Welcome to BBS.") def main(): indexPage() homePage() bbsPage() if __name__ == '__main__': main()
执行结果