文章目录
前言
我的个人网站:https://www.gentlecp.com
python中有一个很经典的用法就是装饰器,它用于在不修改原始函数的情况下,添加新的功能到原始函数中,但是这章内容比较难以理解,本文就从函数到装饰器以及装饰器在现实生产中的应用举出样例,希望能够帮助大家更好地理解装饰器到底有何用处
附:文章个人网站链接
一、函数
谈装饰器前先对函数要有一个深刻理解。在python中,万物皆对象,函数也不例外,我们创建一个函数将其打印出来,看看结果:
def func(message):
print('Here is func: {}'.format(message))
print(func)
可以看到返回值为一个内存地址,说明函数是对象,既然是对象,自然可以进行赋值传递,参数传递操作。
1.1 函数作为对象传递
看下面的例子:
def func(message):
print('Here is func: {}'.format(message))
func_object = func
func_object('Hello world')
我们将func这个对象直接赋值给func_object,用func_object调用,结果成功输出。说明函数可作为对象传递
1.2 函数作为参数传递
看下面的例子:
def func_a(message):
print('Here is func_a:{}'.format(message))
def func_b(func,message):
func(message)
func_b(func_a,'Hello world')
我们将func_a作为func_b的参数传入func_b中,并在func_b中调用该函数,打印输出结果。说明函数可以作为参数传递。
1.3 函数可嵌套
这个大家应该都用过,就是在函数中有时候我们会重复利用一部分代码,而这部分代码在其他地方又不会用到,就可以将这部分整合成一个函数,然后进行调用,看下面的例子:
def func_root(message):
def func_node(message):
print('Here is func_node:{}'.format(message))
# 将func_node结果返回
return func_node(message)
func_root('Hello World')
我们在func_root中又定义了func_node,并在func_root中返回func_node的处理结果。相当于在函数内部调用了内部的函数,说明函数可嵌套。
举一个形象的例子:
老板(func_root)收到外包商一个项目(‘Hello World’)。
外包商:你帮我打印一下Hello World
老板接收原材料(‘Hello World’)。
老板:我不会啊,那什么,小C你帮我打印一下。
然后将原材料(‘Hello World’)扔给小C(= =)。
小C:好的老板,打印了Hello World,写成了报告。
老板一看:哎哟,不错哦。
然后就将结果return给了外包商。
1.4 返回函数本身
前面是内部函数处理了结果,外部函数将结果返回,因为函数本身是对象,自然也可以作为return返回,看下面这个例子:
def func_root():
def func_node(message):
print('Here is func_node:{}'.format(message))
# 将func_node本身返回
return func_node
func_object = func_root()
func_object('Hello world')
同样用上面的例子:
外包商(func_object):你帮我打印Hello World
老板(func_root):我不会啊,我把会打印的人叫来给你用吧,小C你过来一下
外包商:小C,你给我打印Hello World
小C(func_node):print(…)
在代码中,func_object通过func_root这个桥梁,获取到了func_node的地址,这时候它就可以通过()的形式调用func_node的功能。
二、装饰器
2.1 基础装饰器
我们先看一个最基础的装饰器:
def my_decorator(func):
def wrapper():
print("I'm wrapper in my_decorator")
func()
return wrapper
@my_decorator
def hello():
print('hello world')
hello()
其中@my_decorator称为语法糖,其作用等价于
hello = my_decorator(hello)
从结果可以看出,hello成功执行了自己的功能(打印hello world),装饰器装饰了hello函数,并在其基础上添加了功能(打印I’m wrapper in my_decorator)。装饰器一般有两层函数,外层my_decorator用于@装饰在其他函数上,它返回内层函数(wrapper)的地址,让被装饰函数可以直接获取到该地址。
前面说过,有了函数地址,自然可以调用函数功能,所以wrapper可以看作被装饰函数的加强版函数,在其内部必然要调用被装饰函数,且添加一些想添加的代码功能,这使得添加的功能和原始功能互不干扰。
举个例子:
小C:我只会打印hello world,别的我不学,哼~
老板:什么?这么菜怎么完成任务?不行,我给你个神器,这个神器可以自动打印I’m wrapper in my_decorator,也不用你学什么了。但是你要把它戴到头上。以后我布置打印任务的时候,我会发命令给这个神器,这个神器再提示你怎么做知道吗?
小C:好的,老板!
老板:神器啊神器,开始打印吧~
神器:打印I’m wrapper in my_decorator完成,小C,打印hello world
小C:好嘞,打印hello world。
有人可能要问了,为什么不直接用一个函数wrapper,将hello传入其中,然后调用呢?
注意!装饰器的初衷是不影响原始函数的代码和功能,也就是说。假设原始函数hello是一个接口,别人一直用hello作为接口调用,如果你用wrapper接收hello,那么接口的名称就要改动成wrapper,外面的人并不知道这个,还是用hello调用,就会导致出错!而用装饰器的形式,你发现没有,hello接口没有变,但是新功能已经添加进去了。
2.2 带参装饰器
被装饰函数难免会有参数传入,如何将这些参数一并传入到装饰器中呢?看下面的代码:
def my_decorator(func):
def wrapper(*args,**kwargs):
print("I'm wrapper in my_decorator")
func(*args,**kwargs)
return wrapper
@my_decorator
def hello(name):
print('hello world '+name)
hello('CP')
这里用到了args,**kwargs,这样就可以接收任意数量或类型的参数,关于args,**kwargs网上有很多解释的文章,这里不多赘述。可以看到成功地进行了参数传递。
2.3 装饰器自定义参数
前面装饰器一直是接收被装饰函数的参数,那么如果装饰器自己要定义参数呢?例如定义装饰器参数num,用于指定装饰器调用的次数,看下面的代码:
def repeat(num):
def my_decorator(func):
def wrapper():
for i in range(num):
print("I'm wrapper in my_decorator")
func()
return wrapper
return my_decorator
@repeat(5)
def hello():
print('hello world')
hello()
repeat装饰器传入了参数5,用于将他内部的函数执行5次
2.4 类装饰器
类也可以作为装饰器使用,它依赖于函数__call__,实际上,每次调用类的实例,函数__call__便执行一次。看下面的代码:
class CountClass:
def __init__(self, func):
self.func = func
self.calls = 0
def __call__(self, *args, **kwargs):
self.calls += 1
print('calls: {}'.format(self.calls))
return self.func(*args, **kwargs)
@CountClass
def hello():
print("hello world")
hello()
hello()
本质上作用与函数装饰器相同,根据自己的需要选择用函数装饰器还是类装饰器
2.5 装饰器嵌套
装饰器说到底还是函数,因此装饰器本身也可以被装饰,看下面的例子:
import functools
def my_decorator1(func):
def wrapper(*args, **kwargs):
print('Here is decorator1')
func(*args, **kwargs)
return wrapper
def my_decorator2(func):
def wrapper(*args, **kwargs):
print('Here is decorator2')
func(*args, **kwargs)
return wrapper
@my_decorator1
@my_decorator2
def hello(message):
print(message)
hello('hello world')
由结果可以看出来,执行的顺序是decorator1 -> decorator2 -> hello
三、装饰器的应用
装饰器的应用场景有很多,例如登录验证,日志记录,合理性检查等。下面以登录验证为例:假设一个网站有两个功能,登录和评论,而评论功能必须要先检查用户是否登录,登录才能使用否则调用评论会提示需要登录,看下面代码:
IS_LOGIN = False # 全局变量作为是否登录的标志
USER , PWD = 'CP','123456' # 假设只有一个用户
def require_login(func):
def wrapper(*args,**kwargs):
if IS_LOGIN:
# 验证通过,可以评论
func(*args,**kwargs)
else:
# 验证不通过
print('验证失败,您未登录')
login()
return wrapper
# 登录
def login():
global IS_LOGIN
while True:
print('请输入登录用户名:')
user = input()
print('请输入密码:')
pwd = input() # 这里为了简便没有做密码输入加密处理,实际开发需要做
if user == USER and pwd == PWD:
print('登录成功')
IS_LOGIN = True
break
else:
print('用户名或密码输入错误,请重新输入')
# 评论功能
@require_login
def comment():
print('欢迎使用评论功能,请输入你要评论的内容:')
com = input()
print('你的评论是:{}'.format(com))
comment()
comment()
运行结果:
我们定义两个函数和一个装饰器函数,login用于登录,comment用于评论,comment被require_login装饰器装饰,在require_login装饰器中会判断全局变量IS_LOGIN来检查用户是否已经登录,如果登录则执行comment的功能,如果未登录,则提示用户进行登录。
第一次调用comment()提示验证失败,您未登录,并让用户进行登录操作,登录成功后,进入评论的核心功能,用户可以进行评论。
通过上面的例子我们可以发现,comment还是那个comment,我们并没有对其修改,但是让其使用的时候多了一层验证,而且这种方式在不改变接口的同时降低了代码耦合性,使得程序员维护成本大大降低,所以是一种非常值得学习、掌握的方法。
四、总结
关于装饰器的使用,我大概就总结了以上内容,希望能够帮助到你更好地理解装饰器,如果有任何疑问的地方,欢迎在评论区留言或私信给我,我会尽力解答~
建议: 看完文章后一定要自己动手尝试一下才能更好地掌握,不要想当然以为自己看了就会了,动手才是将东西变成自己的的唯一途径!!!