Python装饰器带括号和不带括号的理解

装饰器是 Python 中一个强大且灵活的特性,允许用户在不修改原有函数或类定义的基础上,为其增加额外功能。

今天在尝试自定义 Python 装饰器的时候遇到了一个问题,因为以前一直是使用装饰器,基本没有自定义过装饰器,所以写了一个不是常见的写法(符合装饰器的语法,但是用法上是错误的)

一、Python 装饰器的简单介绍

装饰器本质上是一个可调用对象(通常是函数),它接受一个函数作为输入,并返回一个新的函数作为输出。这个新函数通常会在执行原始函数前后添加额外的操作,从而扩展或改变原始函数的行为。

Python 提供了简洁的语法糖来应用装饰器,即在函数定义之前使用 @my_decorator 的格式。例如:

@my_decorator
def my_function():
    pass

这里的 @my_decorator 实际上是一个语法糖,会将 my_function 传递给 my_decorator 函数,并将返回的结果重新绑定到 my_function 上。因此上诉调用等同于:

def my_function():
    pass

my_function = my_decorator(my_function)

这里需要强调一下,是将 my_function 传递给 @ 后面的整个部分,可以在看完文章之后再返回来理解一下这句话。

二、不带括号和带括号的 Python 装饰器

然后我就写了一个装饰器,不过我犯了一个错误,现在我把代码整理了一下贴了出来。第一种是通常的写法,第二种是我的写法,不过它的调用会有问题。不过这里目前可以看出区别就是一个不带括号,另一个带括号。

import time


def log1(func):
    def wrapper1():
        start = time.time()
        res = func()
        print("exec time: %.2f" % (time.time()-start))
        return res

    print("log1 wrapper1")
    return wrapper1


@log1
def func1():
    time.sleep(0.15)
    print("call func1")


def log2():
    def wrapper2(func):
        start = time.time()
        res = func()
        print("exec time: %.2f" % (time.time()-start))
        return res

    print("log2 wrapper2")
    return wrapper2


@log2()  # 不带括号会报错,提示不需要参数,但是接收到了一个参数
def func2():
    time.sleep(0.15)
    print("call func2")


if __name__ == '__main__':
    func1()

    # 如果不使用装饰器,那么:
    # func1 的调用方式等价于  func1 => log1(func1),这还是一个函数
    # func2 的调用方式等价于 func2 => log2()(func2),这不是一个函数,而是一个结果了

在这里插入图片描述

如果不使用装饰器语法,而是普通的 Python 代码的方式是这样的(结果同上):

import time


def log1(func):
    def wrapper1():
        start = time.time()
        res = func()
        print("exec time: %.2f" % (time.time()-start))
        return res

    print("log1 wrapper1")
    return wrapper1


def func1():
    time.sleep(0.15)
    print("call func1")


def log2():
    def wrapper2(func):
        start = time.time()
        res = func()
        print("exec time: %.2f" % (time.time()-start))
        return res

    print("log2 wrapper2")
    return wrapper2


def func2():
    time.sleep(0.15)
    print("call func2")


if __name__ == '__main__':
    func1 = log1(func1)
    func1()

    log2()(func2)

对于装饰器用法的代码,虽然没有调用 func2(),但是它就会直接执行,而且调用 func2() 会报错。

@log2()  # 不带括号会报错,提示不需要参数,但是接收到了一个参数
def func2():
    time.sleep(0.15)
    print("call func2")

在这里插入图片描述

不过这个错误反而提醒了我,装饰器的原理应该是:在程序运行时,会将被装饰的函数作为参数传递给装饰器,也就是 @ 后的整个部分。

因此对于 @log1 来说,这就相当于:func1 = log1(func1),所以之后执行 func1 就是被装饰后的函数了(可以观察到打印输出的语句)。函数是直接传到了装饰器内部,这样比较容易理解。

对于 @log2() 来说,这就相当于:log2()(func2),函数是传到了 log2() 返回的函数中了(这里是 wrapper2),这样它的结果就不是一个函数了(非 callable),而是一个具体的值了(这里的结果为 None)。如果我使用 @log2,那么就是把函数传到 log2 中,但是这个函数是无参数的,如果给它传递参数就会报错了。参数是传递给它返回的函数中,所以装饰器需要加上括号调用,即 @log2()。不过这里的写法犯了一个错误,因此导致了导致了它直接执行了,因为它返回的不是一个函数,而是它的执行结果了。所以,解决的方式就是在内部再嵌套一层函数,修改之后的代码如下:

def log2():
    def wrapper2(func):
        def wrapper3():
            start = time.time()
            res = func()
            print("exec time: %.2f" % (time.time()-start))
            return res

        return wrapper3

    print("log2 wrapper2")
    return wrapper2


@log2()
def func2():
    time.sleep(0.15)
    print("call func2")


if __name__ == '__main__':
    func2()
    # 等价于 func2 = log2()(func2) 这里它还是一个函数(可调用),而不是一个返回值了()

在这里插入图片描述

我认为这里最大的问题就是这个装饰器的语法糖实在是太便利的,这样对于使用是非常方便的,但是凡事有好处就有坏处,它反而不利于我们对它的理解了。特别是,我之前有过 Java 的注解使用和学习经验。如果只是简单的使用确实不需要理解它是怎么工作的,但是对于想要深入理解的同学来说,还是需要去了解背后的运行机制。这里要把握的一点就是:在运行时,会将被装饰的函数作为参数传递给 @ 后面这整个部分,如果不带括号就是直接作为参数传输传入,然后返回一个新的函数。如果带括号,就是传递给它的返回值(内层函数,所以内层函数还要再嵌套才行,不然就是直接执行函数了)。所以即使被装饰的函数在内层,它的执行也是没有问题的,但你要明白这个过程!

  • 19
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值