什么是装饰器
装饰器是 Python 编程中的一个概念,或者可以简单的认为装饰器就是一个函数。主要的作用是用来对原函数的功能进行扩展,使程序的代码变得更见简介。目的是在不改变原函数名的情况下,给函数赋予新的功能。而装饰器与平常函数的不同之处在于,装饰器的返回值也是一个函数。
函数对象
在 Python 中可以将函数当作一个对象来对待,可以直接用函数名对对象进行赋值,从而使该对象具有和函数相同的功能。
def func(ch = "hello world"):
print(ch)
f = func
func()
# hello world
func("hhhh")
# hhhh
f()
# hello world
f("hhhh")
# hhhh
这里我们将变量赋予了函数名,因此 f() 就变成了函数调用,具有和自定义函数相同的功能。
函数定义嵌套
定义函数的嵌套调用可能比较常见,但是在 Python 还可以实现函数的嵌套定义。
def func(ch = "hello world"):
print(ch)
def func1(ch = "hello world"):
print(ch,"func1()")
def func2(ch = "hello world"):
print(ch,"func2()")
func1()
func2()
f = func
func()
# hello world
# hello world func1()
# hello world func2()
func("hhhh")
# hhhh
# hello world func1()
# hello world func2()
f()
# hello world
# hello world func1()
# hello world func2()
f("hhhh")
# hhhh
# hello world func1()
# hello world func2()
func2()
# error
但很显然,我们进行函数调用时必须要考虑到变量名的作用域。这里调用局部作用域中的函数 func2 是错误的。
函数返回值
既然函数可以被当成一个对象来进行赋值,那么将函数作为函数返回值也是可以的。
def func(ch = "hello world"):
print(ch)
def func1(ch = "hello world"):
print(ch,"func1()")
return func1
f = func
h = func()
f()
# hello world
h()
# hello world
# hello world func1()
func()()
# hello world
# hello world func1()
从上边的程序中可以看到,f,h 的赋值对象十分相似,但是执行结果却有相当大的区别,为什么呢?
对 f 赋值的时候用了函数句柄 func,此时 f 就变成了函数 func,因此执行 f() 便是执行了 func() 函数调用。
而对 h 赋值用的却是函数 func 的返回值,而函数 func 的返回值是 func2,因此 h 就变成了函数 func2,而 h() 就变成了 func2() 函数调用。此时便利用函数作为返回值实现了嵌套函数的调用。h() 和 func()() 的作用是一样的。
函数作为函数参数
同上可以采用函数作为参数调用的参数来实现函数的传递。
def func(ch = "hello world"):
print(ch)
def func1(ch = "hello world"):
print(ch,"func1()")
return func1
def func2(f):
f()
func2(func)
# hello world
此时就相当于执行了如下操作:
f = func
f()
# hello world
装饰器实现
首先写一个简单的装饰器实现的示例,此处并没有使用到 @ 来实现装饰器定义,但是执行过程却是和装饰器一样的操作。
def func(f):
print("func()")
def func1():
print("front func1()")
f()
print("behind func1()")
return func1
def func2():
print("func2()")
func2()
# func2()
f = func(func2)
# func()
f()
# front func1()
# func2()
# behind func1()
接下来,使用 @ 作为装饰器标识符,对函数进行装饰器定义。
def func(f):
print("func()")
def func1():
print("front func1()")
f()
print("behind func1()")
return func1
@func
def func2():
print("func2()")
func2()
# func()
# front func1()
# func2()
# behind func1()
上边便是用装饰器进行的函数封装。可以看到,与之前用 f 进行函数赋值对比,封装过后的程序变得简洁了很多,并且执行过程也是一样的。其实对上面代码进行分析可以知道:func2() = func(func2)()
装饰器的调用顺序
有时我们会考虑用多个装饰器对函数进行修饰,此时要注意装饰器的调用顺序。
def func1(f):
print("func1()")
def func2():
print("front func2()")
f()
print("behind func2()")
return func2
def func3(f):
print("func3()")
def func4():
print("front func4()")
f()
print("behind func4()")
return func4
@func1
@func3
# @func3
# @func1
def func():
print("func()")
func()
# func3()
# func1()
# front func2()
# front func4()
# func()
# behind func4()
# behind func2()
# func1()
# func3()
# front func4()
# front func2()
# func()
# behind func2()
# behind func4()
从上面的结果可以看出,上面的调用顺序为从上往下的,即 func() = func1(func3(func))()。
带参数的装饰器
之前通过调用装饰器来修饰函数,从而能够不用重复修改函数或者增加新的封装。实现了程序的可重复利用性,增加了程序的可读性。但是如果使用装饰器修饰的函数本身是有参数的,又该怎么办呢?
def func(f):
print("func()")
def func1(ch):
print("front func1()")
f(ch)
print("behind func1()")
return func1
@func
def func2(ch = "here"):
print(ch,"func2()")
func2("hello")
# func()
# front func1()
# hello func2()
# behind func1()
现在假设函数 func2 是有参的,我们在 func2 中传入了一个参数 ch = ”hello“,同时在函数 func1 也变成了有参的,参数也是 ch。而此时在函数 func1 内的 f 即为 func2,因此也是有参的,也应变为 ch(其实这里参数名称不一定非要相同,只要保证在一个函数中函数名时一致的即可)。从上面我们可以看出 func2(ch) = func(func2)(ch)
参数个数不确定的装饰器
从上面的过程可以看出,修改被装饰函数后也要对装饰函数进行修改,而参数数目不确定时,这样的修改也会变得不确定,因此便希望有一种好的方法能够避免这种修改。这里使用 *args, **kwargs 来实现。
def func(f):
print("func()")
def func1(*args, **kwargs):
print("front func1()")
f(*args, **kwargs)
print("behind func1()")
return func1
@func
def func2(ch1 = "here",ch2 = " here"):
print(ch1,ch2,"func2()")
@func
def func3(ch = "here"):
print(ch,"func2()")
func2("hello","world")
# func()
# front func1()
# hello world func2()
# behind func1()
func3("hello")
# func()
# front func1()
# hello func2()
# behind func1()
有时候我们会以为会出现上面的运行结果,但是实际的运行结果如下:
func()
func()
front func1()
hello world func2()
behind func1()
front func1()
hello func2()
behind func1()
这是因为在实际的运行过程中,带有装饰器的函数和普通函数的执行顺序不同。有关装饰器的代码执行顺序可以看这篇文章。
装饰器带参数
除了被修饰函数可以带参数外,修饰函数即装饰器本身也是可以带参数的,此时装饰器的用法又有不同。
def func1(ch):
print(ch, "func()")
def func2(f):
print("func2()")
def func3(*args, **kwargs):
print("front func3()")
f(*args, **kwargs)
print("behind func3()")
return func3
return func2
@func1(ch = "func")
def func(ch1 = "here",ch2 = " here"):
print(ch1,ch2,"func2()")
func("hello","world")
# func func()
# func2()
# front func3()
# hello world func2()
# behind func3()
可以看到,装饰器带参数时,函数比平常的多嵌套了一层,这是因为最外层的嵌套函数需要传递装饰器本身的参数,然后返回的是带有装饰器的函数句柄,从而再执行经过装饰的函数,此时的执行过程和之前的相同。上面的语句 func("hello","world") = func1(ch = "func")(func)("hello","world")
装饰器的常见使用方式
如果我们不知道装饰器是否含有参数,也就是说当我们实际使用的时候从能够确认修饰函数调用的版本,这就要求我们提前对修饰函数进行定义,而不管是有参还是无参。我们可以用分支结构实现这个功能。
def func1(arg):
if callable(arg):
def func2(*args, **kwargs):
print("front func2()")
arg(*args, **kwargs)
print("behind func2()")
return func2
else:
print(arg,"func1()")
def func2(f):
def func3(*args, **kwargs):
print("front func3()")
f(*args, **kwargs)
print("behind func3()")
return func3
return func2
@func1
# @func1("func")
def func(ch1 = "here",ch2 = " here"):
print(ch1,ch2,"func2()")
func("hello","world")
# front func2()
# hello world func2()
# behind func2()
# func func1()
# front func3()
# hello world func2()
# behind func3()
上面的使用方法在很多封装库中会经常出现。
装饰器的元信息
对于装饰器的使用来说,主要就是上边所说的了。但有时我们可能会使用到原函数的元信息,如 __name__等。
def func1(ch):
print(ch, "func()")
def func2(f):
print(f.__name__,"func2()")
def func3(*args, **kwargs):
print("front func3()")
f(*args, **kwargs)
print("behind func3()")
return func3
return func2
@func1(ch = "func")
def func(ch1 = "here",ch2 = " here"):
print(ch1,ch2,"func2()")
print(func.__name__)
func("hello","world")
# func func()
# func func2()
# front func3()
# hello world func2()
# func3
# behind func3()
如结果的最后一行中所示,func.__name__ 的结果变成了 func3,这跟我们预想的是有区别的。因此我们可能希望保留原来的元信息不变。
为了改变上面的结果,我们借助外部工具实现。
import functools
def func1(ch):
print(ch, "func()")
def func2(f):
print(f.__name__,"func2()")
@functools.wraps(f)
def func3(*args, **kwargs):
print("front func3()")
f(*args, **kwargs)
print("behind func3()")
return func3
return func2
@func1(ch = "func")
def func(ch1 = "here",ch2 = " here"):
print(ch1,ch2,"func2()")
print(func.__name__)
func("hello","world")
# func func()
# func func2()
# front func3()
# hello world func2()
# func
# behind func3()
类装饰器
装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器的灵活度更高,封装性更好。类装饰器的使用主要是凭借类的 __call__ 方法实现。当用 @ 将类装饰器附加到某个函数上时,就会调用这个方法。类装饰器的使用方法和函数装饰器类似。
class fun():
def __int__(self,info = "none"):
self.info = info
def __call__(self, f):
def func(*args, **kwargs):
print("func()")
f(*args, **kwargs)
return func
@fun()
def func1(ch1 = "here",ch2 = " here"):
print(ch1,ch2,"func1()")
func1("hello","world")
# func()
# hello world func1()
需要注意的是,类在无参的时候也需要加括号。在继承类中也可以实现类装饰器的使用,只是需要改变 @ 后类名。此处不做赘述。
内置装饰器
在 Python 中,还存在着内置装饰器。具体的使用方法可以参见这篇文章。