Python 装饰器详解

285 阅读6分钟

「这是我参与2022首次更文挑战的第15天,活动详情查看:2022首次更文挑战」。

函数

Python 不是严格的面向对象语言,很多时候我们会选择面向函数的方式进行编程。那函数是什么呢?

我们可以把函数当作一个黑盒子,我们输入一些数据,函数会帮我们处理,然后返回一个结果。在 Python 中,函数通过 def 关键字定义,定义的格式如下:

def 函数名(参数列表):
    函数体
    return 返回值

其中参数列表和返回值不是必须的,但是如果函数体不执行任何操作,那我们需要用一个 pass 占位:

def func(a, b):
    return a + b

上面我们定义了一个函数 func,参数为 a、b,返回值是 a 和 b 的和。在定义函数后,我们就可以使用它了:

def func(a, b):
    return a + b


result = func(1, 2)
print(result)

相信上面这些知识大家都非常熟悉。在 Python 中,函数的定义是可以嵌套的,也就是说我们可以在函数里面定义函数:

def func(a, b):

    def inner():
        return 1
    
    return a + b


在上面的代码我们在 func 里面定义了一个 inner 函数,这个时候我们需要注意,因为 inner 函数定义在 func 里面,所以我们只能在 func 里面使用 inner 函数,我们可以尝试运行下面的代码:

def func(a, b):

    def inner():
        return 1

    return a + b

inner()

这个时候就会出现如下错误:

NameError: name 'inner' is not defined

意思就是 inner 没有定义,其实它的意思就是在当前作用域中没有定义。那我们有没有办法在全局使用局部定义的函数呢?

闭包

在 Python 中,函数也可以作为参数。我们可以执行下面的代码:

def func(a, b):
    return a + b


print(func)

我们直接输出函数名,而没有加括号。输出结果如下:

<function func at 0x000002C83CC96678>

可以看懂结果是一个 function 的对象,我们可以把它赋值给另外的变量:

def func(a, b):
    return a + b


my_func = func
print(my_func(1, 3))

我们把 func 赋值给 my_func 变量,然后发现 my_func 可以和 func 一样使用。输出结果如下:

4

结果和 func 的结果一样。那我们能不能把函数当做参数或者返回值呢?我们可以尝试一下:

def func():

    def inner():
        print("执行了 inner 函数")

    return inner


in_func = func()
in_func()

我们在 func 里面定义了一个 inner 函数,按理我们是不能在外部调用 inner 函数的,但是我们把 inner 当作返回值返回到外部,这样我们就能在外部调用 inner 函数了。我们来看看运行结果:

执行了 inner 函数

可以看到确实执行了 inner 函数。其实 inner 函数我们就可以叫做闭包函数。那利用闭包,我们能干什么呢?我们可以利用闭包来实现装饰器。那装饰器又是什么呢?下面我们来详细看一下。

装饰器

装饰器如果从字面意思来讲就是用来装饰的东西,它装饰的对象是函数。我们可以用装饰器在不改变原函数的情况下对函数进行扩展。我们先不说装饰器,我们想想要怎么才能扩展一个函数,最简单的办法就是如下:

def func(a, b):
    return a + b


print("在 func 之前执行")
func(1, 2)
print("在 func 之后执行")

我们直接调用这个函数,然后在函数前面添加一些代码,再在函数执行后面添加一些代码。这样很好的完成了我们的任务,但是这种方式属实有点矬略。如果我们经常要用到这种扩展,或者多个函数需要用到这样的扩展我们这种方式就束手无策了。

这个时候我们就可以使用闭包,我无法解释为什么要使用闭包,但是闭包能很好的解决这个问题。我们看下面这段代码:

def say_hi(func):
    print("nice to meet you")

    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    print("good bye")
    return inner


def func(a, b):
    return a + b

say_hi_func = say_hi(func)

我们先定义一个函数 say_hi,它的参数是一个函数,也就是我们要扩展的函数。然后我们在里面定义一个 inner 函数,在 inner 函数中完成 func 函数的调用,并接收 func 的参数和返回值。最后将 inner 函数作为参数返回。

我们想对 func 函数进行扩展,我们只需要调用 say_hi(func),它就会给我们返回一个加强后的函数。我们可以执行看看:


def say_hi(func):
    print("nice to meet you")

    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    print("good bye")
    return inner


def func(a, b):
    return a + b

say_hi_func = say_hi(func)
result = say_hi_func(1, 2)
print(result)


执行结果如下:

nice to meet you
good bye
3

可以看到我们成功扩展了 func 函数。但是这样有点混乱,我们还需要先加强函数才能使用,而另一种简单的方式就是使用 @ 符号:


def say_hi(func):
    print("nice to meet you")

    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    print("good bye")
    return inner


@say_hi
def func(a, b):
    return a + b


print(func(1, 2))

我们在 func 函数定义的时候执行了添加了 @say_hi,这个时候 func 就是解释器会帮我们完成下面几句代码:

func = say_hi(func)

这样就不需要再使用一个新的函数了。

多装饰器

我们可以给一个函数添加多个装饰器,其实使用也是一样的。我们可以这样里面,假如我们有如下函数:

def func():
	print("这是函数体")

假如我们有如下装饰器:

def decorate(func):
	print("函数执行前")
	def inner(*args, **kwargs):
		return func(*args, **kwargs)
	print("函数执行后")
	return inner

在外面对 func 函数进行装饰后,func 的内容变为:

def func():
	print("函数执行前")
	print("函数体")
	print("函数执行后")

这时使用装饰器后的函数和普通函数没有区别,外面依旧可以用同样的方式给他添加多个装饰器:


def dec1(func):
    print("start dec1")
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    print("end dec1")
    return inner

def dec2(func):
    print("start dec2")
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    print("end dec2")
    return inner

@dec2
@dec1
def func(a, b):
    return a + b

print(func(1, 2))

不过外面需要注意一下生效的顺序:

start dec1
end dec1
start dec2
end dec2
3

可以看到时 dec1 装饰器先起作用,对于多个装饰器也是一样的。

带参装饰器

其实装饰器也是可以带参数的,我们可以定义一个更加复杂的装饰器:


def dec1(name):
    def decorator(func):
        def inner(*args, **kwargs):
            if name == "zack":
                print("hi zack")
            elif name == "rudy":
                print("hi rudy")
            return func(*args, **kwargs)
        return inner
    return decorator


@dec1("zack")
def func(a, b):
    return a+b


print(func(1, 2))

我们先来看下面的代码:

@dec1("zack")
def func(a, b):
	return a +b 

我们可以对 @dec1("zack") 进行如下理解,其中 dec1("zack") 的操作是执行 dec1 函数,并传入参数,而 dec1 的返回值是一个装饰器。这样我们就很好理解了。

因此,我们需要在 dec1 函数中定义一个装饰器。这可以从代码中看到。其它部分和之前没有区别,下面是输出结果:

hi zack
3

这种带参数的装饰器可以让我们的装饰器更加灵活,我们只需要通过不同参数就能让装饰器装饰不同的效果。