Python的语法糖——装饰器(上)

对于很多刚刚接触Python的人来说,对装饰器的概念会感到非常模糊,因为几乎没有使用@作为语法标识的语言,本人唯一能想起来的也只有Java中的注释会以@来进行标识了。

装饰器是什么

装饰器是一种在不修改原函数的基础上给函数增加额外功能的方法。
在翻看别人的Python源代码的过程中,你肯定见到过一些这样的用法

# 有这样的
@decorator1
def add(x, y):
    return x + y

# 也有这样的
@decorator2(arg1, arg2)
def add(x, y):
    return x + y

在上面的代码中,两行以@开头的代码@decorator1@decorator2(arg1, arg2)就是我们今天要介绍的主角——装饰器

装饰器能做些什么

常见的装饰器功能分为如下几种:

  1. 记录函数的执行时间
  2. 记录函数的调用次数
  3. 缓存函数的结果
  4. 权限控制
  5. 类型检查

装饰器的原理是什么

首先我们要明确一个概念,在Python中,万物皆对象,所有东西其实都是object,函数也不例外。
装饰器的本质就是一个函数,它接受一个函数作为参数,并返回一个新的函数。
原函数可以在新函数内部被调用,并且新函数可以在原函数之前或之后添加新的功能。

当使用@decorator语法将装饰器应用于一个函数时,Python会执行如下操作:

  1. 首先调用装饰器函数,将原函数作为参数传入
  2. 装饰器函数返回一个新函数,并将原函数包装在新函数中
  3. Python 会将原函数的定义替换为新函数的定义

写个简单的装饰器吧

说了这么多的理论,接下来实战一下吧。
有没有这样一种需求,我们在执行代码时,想要知道某函数耗时是多少。通常第一反应想到的是如下实现方法

import time

def my_func():
    start = time.time()
    
    # 程序的主逻辑
    ...
    
    end = time.time()
    print(f"function 'my_func' spend {end - start} second.")

my_func()

# function 'my_func' spend x.xxxx seconds.

一个函数还好说,如果我们有多个函数都需要进行计时,岂不是要复制好几份代码了?这可一点也不Pythonic!
如果引入装饰器那就不同了,代码一下子就能变得清晰易读(省的后人骂你)。想对哪个函数计时,直接在这个函数头上加个计时装饰器就可以了。

import time

def timer(func):
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print(f"function '{func.__name__}' spend {end - start} seconds.")
    return wrapper

@timer                  # 添加计时用装饰器
def sleep_3():
    time.sleep(3)
    
@timer                  # 添加计时用装饰器
def sleep_5():
    time.sleep(5)

sleep_3()
sleep_5()

# function 'sleep_3' spend 3.0012123584747314 seconds.
# function 'sleep_5' spend 5.0002405643463135 seconds.

我们来分析下这个例子

其实很好理解,其实等于将计时的核心逻辑抽象出来进行了复用。
sleep_3函数举例,在添加装饰器后,等价调用形式变为了timer(sleep_3())。所以装饰器只做了以下三件事:

  1. 将原有函数逻辑保存在timer函数的func参数中。
  2. 装饰器通过return wrapper,将sleep_3的功能重定向到了装饰器内部的wrapper函数。
  3. 将原有函数逻辑func插入在wrapper函数计时逻辑的中间运行。

看到这里就有人说了,上面的两个例子的函数都没有传参,平时使用函数基本都需要传递参数,但是加上参数这个装饰器就会报错,是不是写的有问题?
别急,我们继续往下看。

写个复杂点的装饰器

其实上面这个装饰器是最简单的版本(阉割版),下面我们来实现更加完整的pro版。
换个需求,这次我们还是需要打印函数运行的耗时,只不过这次的函数会带有参数,并且参数的情况不确定。接下来我们对装饰器进行下改造。

import time

def timer(func):
    def wrapper(*args, **kwargs):       # 给内部函数
        start = time.time()
        ret = func(*args, **kwargs)
        end = time.time()
        print(f"function '{func.__name__}' spend {end - start} seconds.")
        return ret
    return wrapper

@timer                  # 添加计时用装饰器
def add(x, y):
    return x + y
    
@timer                  # 添加计时用装饰器
def square(x):
    return x ** 2

print(add(1, 2))
print(square(3))

# function 'add' spend 1.0006356239318848 seconds.
# 3
# function 'square' spend 2.000314474105835 seconds.
# 9

按照惯例,我们来分析下这个例子

其实这个例子只是在上个例子的基础上,对被装饰的函数支持了传参的操作,核心仍是将计时的核心逻辑抽象了出来。
以函数add举例,在添加装饰器后,等价调用形式变为了timer(add(x, y))
而在装饰器内部,发生了以下几件事:

  1. 将原有函数逻辑保存在timer函数的func参数中。
  2. 装饰器通过return wrapper,将sleep_3的功能重定向到了装饰器内部的wrapper函数。
  3. 由于函数进行了重定向,原有add函数中的形参就被传递给了重定向后的函数wrapper,再将原有函数逻辑func插入在wrapper函数计时逻辑的中间运行。

最终经过装饰的函数,也会返回原函数add的执行结果,也就做到了一个使用无感可传参的装饰器。

看到这里有人就说了,装饰器大部分时间都被定义成了一个全局的命名空间进行使用,也就是定义在文件当中的最外层。但是我们也都知道,并非所有的东西都是适合放到全局的命名空间当中。
是不是你也遇到过这种需求,有很多装饰器想给他们分类和封装到类中,或者希望将其中的某些装饰器抽象到类中
不知道你是不是也曾经和我一样,尝试翻遍了全网的教程,但最终还是由于各种坑放弃了。
别急关注我,下面一篇你就会知道,原来实现的方式这么简单!

关注公众号

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值