Python 中装饰器的用法

什么是装饰器

装饰器是 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

此时就相当于执行了如下操作:

扫描二维码关注公众号,回复: 9583756 查看本文章
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 中,还存在着内置装饰器。具体的使用方法可以参见这篇文章。

发布了77 篇原创文章 · 获赞 5 · 访问量 4919

猜你喜欢

转载自blog.csdn.net/SAKURASANN/article/details/102983043