文章目录
- 前言
- 一、函数
- 1.1 函数作为对象传递
- 1.2 函数作为参数传递
- 1.3 函数可嵌套
- 1.4 返回函数本身
- 二、装饰器
- 2.1 基础装饰器
- 2.2 带参装饰器
- 2.3 装饰器自定义参数
- 2.4 类装饰器
- 2.5 装饰器嵌套
- 三、装饰器的应用
- 四、总结
前言
我的个人网站: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
(
)
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,我们并没有对其修改,但是让其使用的时候多了一层验证,而且这种方式在不改变接口的同时降低了代码耦合性,使得程序员维护成本大大降低,所以是一种非常值得学习、掌握的方法。
四、总结
关于装饰器的使用,我大概就总结了以上内容,希望能够帮助到你更好地理解装饰器,如果有任何疑问的地方,欢迎在评论区留言或私信给我,我会尽力解答~
建议:
看完文章后一定要自己动手尝试一下才能更好地掌握,不要想当然以为自己看了就会了,动手才是将东西变成自己的的唯一途径!!!