Python中有一种较为高级的语法叫做装饰器。顾名思义,装饰器就是在原有的基础上对其进行装饰,使其更加实用或者美观。在Python中,装饰器的作用定义为:在不改变原函数代码的前提下,给原函数增加新的功能。
我们来看下面这个例子:
def func():
x = input()
print(x)
return 0
res = func()
我们定义了一个func函数,功能是从键盘输入一个值赋给x,然后输出这个值。现在func函数已经写好了,我们需要增加一个功能,就是在输入之前要给一个提示信息,告诉用户现在应该输入x的值了,在不改变原函数代码的情况下,这个问题该如何解决呢?
接下来会用到函数作为参数传递给另一个函数,不懂的小伙伴们可以参考:Python中将函数作为参数传递给函数-优快云博客
这个时候就非常符合我们装饰器的定义:在不改变原函数代码的情况下,增加原函数的功能。
def func():
x = input()
print(x)
return 0
def printer(func_name): #将原先的func函数作为参数传递给printer函数,形成闭包,不传会发生无限递归调用
def wrapper():
print("请用户输入:")
fun = func_name()
return fun
return wrapper
func = printer(func)
res = func()
具体原理是:我们定义了一个printer函数,接受func函数作为参数,接着我们在printer函数里面定义了一个wrapper函数,功能是先输出一个“请用户输入”的提示语,来满足我们最开始的要求,接着调用原始的func函数,并将结果存在fun中,接着返回的是一个函数对象fun。最外层返回的是wrapper函数对象。
当我们在执行func = printer(func)的时候,首先是把原始的func函数传递给printer函数,然后printer函数返回一个wrapper函数对象,相当于是对wrapper函数进行调用,当调用wrapper函数的时候,就会先输出一个“请用户输入:”的语句。紧接着会调用原始的func函数,把其结果存为fun,此时fun的值是0(因为原始的func返回值是0),但更重要的是fun也是一个函数对象,功能和原始func一样。然后返回这个fun,返回这个fun的时候就是对fun进行调用,就会执行一遍原始的func函数。所以执行完func = printer(func)的时候,func这个名字已经绑定到printer的这个函数对象上面去了。所以当我们res = func()的时候,就不是调用原始的func,而是调用新绑定的函数对象printer。而printer的功能就是先输出“请用户输入:”,在调用原始的func。这样就能达到我们最开始的要求。
为了简化这一过程,Python提供了一个语法糖(@函数名),当我们在一个函数上面写上@函数名的时候,Python就会帮我们自动执行func1 = func2(func1):
def func2(func1):
pass
@func2
def func1():
pass
等价于
def func2(func1):
pass
def func1():
pass
func1 = func2(func1)
有的小伙伴可能会说,这个装饰器的复杂程度太高,还不如直接在原函数里修改。其实不然,试想这样一个场景:有一万个输入函数,都需要你去添加一个提示语,那么你就需要对这一万个函数进行修改,如果使用装饰器,就可以很方便的实现这一诉求。
刚刚讲的是没有参数的函数装饰器。那么有参数,我们应该怎么做呢?
先想想有无参数的区别在哪?
其实最主要的是在我们之前定义的wrapper函数中,我们使用fun = func()这里的时候就没有给func()传参数,但是现在问题来了,如果我的func原本就需要参数,且多个func都需要加入修饰器,每个func的参数个数还不一样,那怎么办呢?
其实也很好处理,只需要使用Python的打包和解包即可实现,把原装饰器代码改为:
def printer(func_name):
def wrapper(*args,**kwargs):
print("请用户输入:")
fun = func_name(*args,**kwargs) #打包与解包的使用
return fun
return wrapper
不是很理解打包与解包的可以参考:Python:*args与**kwargs的作用【打包与解包】-优快云博客