Python 装饰器是什么?
在 Python 中,我们可以使用装饰器(decorator)来增强一个函数的功能。所谓的装饰器,可以理解成一个 “增强器”,它就是用来 “装饰”(增强)一个函数的。
装饰器一般用来给函数添加一些额外的功能,比如:计算函数执行时间、记录日志、建立和撤销环境、缓存、权限校验等。
提示: 绿叶网的 Python 在线编译器会进行超时限制(出于安全问题),因此对于本节示例,小伙伴们应该到本地编辑器(如 VSCode)中运行测试。
示例 1:计算函数执行时间
import time
# 定义装饰器
def gettime(fn):
def inner(*args, **kwargs):
# 获取开始时的时间戳
start = time.time()
result = fn(*args, **kwargs)
# 获取结束时的时间戳
end = time.time()
print('Running time:', (end - start))
return result
return inner
# 使用装饰器
@gettime
def getsum(n):
result = 0
for i in range(n+1):
result += i
return result
# 调用函数
getsum(100000000)运行结果如下。
Running time: 5.847348690032959分析:
小伙伴们要清楚,装饰器本质上也是一个函数。getsum() 函数定义的上方添加一个 @gettime,就表示使用 gettime() 这个装饰器来增强 getsum() 函数的功能。此外,这里的执行时间有一定出入,这个是正常的。
对于这个例子来说,下面两种方式是等价的。
# 方式1
@gettime
def getsum(n):
result = 0
for i in range(n+1):
result += i
return result
# 方式2
def getsum(n):
result = 0
for i in range(n+1):
result += i
return result
getsum = gettime(getsum)在 getsum() 定义的上方加上 @gettime,就相当于执行 getsum=gettime(getsum)。理解了装饰器的使用方法,我们再来一步步理解它的定义过程。
首先装饰器是一个函数,参数也是函数,并且返回值还是函数。def inner(*args, **kwargs): 是约定俗成的固定写法,这里其实使用了参数解包的语法。*args 会收集所有的位置参数,而 **kwargs 会收集所有的关键字参数。
对于 gettime() 来说,如果传进来的 fn 函数没有返回值,那么在 gettime() 的 inner() 这个函数中,就不需要返回一个值,请看下面例子。
示例 2:函数没有返回值
import time
# 定义装饰器
def gettime(fn):
def inner(*args, **kwargs):
start = time.time()
result = fn(*args, **kwargs)
end = time.time()
print('Running time:', (end - start))
return inner
# 使用装饰器
@gettime
def getsum(n):
result = 0
for i in range(n+1):
result += i
print(result)
# 调用函数
getsum(100000000)运行结果如下。
Running time: 5.476354122161865分析:
在上面示例中,getsum() 函数没有返回值,所以在 gettime() 的 inner() 函数中,就不需要使用 return 来返回一个值。
细心的小伙伴们可能也发现了,上面定义的 @gettime 与 “类与对象” 中介绍的 @classmethod 和 @staticmethod 很相似。确实没有错,@classmethod 和 @staticmethod 这两个本质上也是装饰器,只不过它们是 Python 内置好的装饰器而已。
提示:
- 装饰器本质上是一种特殊的函数,它接受一个函数作为参数,并返回一个新函数。
- 装饰器可以用于函数、方法和类。
- 一个函数可以同时被多个装饰器装饰,执行顺序是从最靠近函数的装饰器开始,由内向外依次执行。
- 装饰器在定义时立即执行,而不是在被装饰函数被调用时执行。
Python 内置装饰器
在 Python 中,Python 内置装饰器有 3 种,如下表所示。
| 装饰器 | 说明 |
|---|---|
| @classmethod | 将一个方法标记为类方法 |
| @staticmethod | 将一个方法标记为静态方法 |
| @property | 用于创建属性(是一个 “语法糖”) |
