Python进阶之装饰器

前言

上一章我们讲了函数的本质,今天我们谈谈python的装饰器,这是一个现实开发中很常用的东西,快快学起来吧~

需要函数本质作为理论基础,忘了的童鞋翻一翻:Python进阶之函数的那些事


装饰器基础知识

装饰器定义:装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。 装饰器会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。

但是装饰器是有限制的:装饰器不能修改被装饰函数的源代码逻辑、装饰器不能修改被装饰函数的调用方式。


接下来,我们先看看学习装饰器需要的功能储备吧:

  1. 函数即变量(之前讲过了,这点略过)
  2. 高阶函数
  3. 嵌套函数 + 闭包



高阶函数

当函数可以满足以下这两个条件之一,就可以称为高阶函数。

①把一个函数名当作实参传给另一个函数

  • 如上图,就是高阶函数的一种,他将hello函数传给了show_time,完成了函数运行计时功能
  • 它实现了不修改另一个函数源代码的前提下为其添加功能,但是改变了函数的调用方式

②函数的返回值是另一个函数

  • 这也是高级函数的一种,它实现了不修改函数的调用方式,为其函数添加新功能这一特点。
  • 但却有局限性,添加的功能全部在运行功能函数的上方,不灵活。

这两个高级函数都实现了装饰器一部分的功能,我们需要其他知识点来完善它。



嵌套函数

嵌套函数的定义是,在一个函数内又有用def 声明的另一个函数。

嵌套函数是这样的:

1
2
3
4
5
6
7
8
9
def foo():
print('in the foo')

def bar():
print('in the bar')

bar() # 处于foo函数体中,如果要运行,就需要先将bar函数定义

foo()

闭包

函数的内部引用了函数外部的变量,这就是闭包。

假如有个名为 avg 的函数,它的作用是计算不断增加的系列值的均值;例如,整个历史中某个商品的平均收盘价。每天都会增加新价格,因此平均值要考虑至目前为止所有的价格。

我们使用高阶函数进行编写这个函数的功能:

可以看到,我们实现了功能,但是这里面隐藏了个细节——闭包!

运行此函数时,返回嵌套函数的引用,那么 agv=make_averager()就得到了一个对象(averager的内存地址),问题来了,为什么函数内部还可以调用已经运行完毕的外部函数体中的series列表?不应该是函数运行之后,该函数体就跟着销毁吗?

原因是这样的:在 averager 函数中,series 是自由变量(free variable)。自由变量指未在本地作用域中绑定的变量。

闭包它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。



一个装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import time

def foo(func):
def bar(name):
start_time = time.time()
time.sleep(3)
func(name) # run函数实际的运行,是在这里
stop_time = time.time()
run_time = stop_time - start_time

print("运行耗费时间:", run_time)

return bar


@foo # 相当于run = foo(run)
def run(name):
print("I am %s" % name)


run("zihao")

运行结果如下:

  • run是功能函数的函数名,表示下面的函数将作为实参传入foo。
  • @后面跟的是添加新功能的函数(也就是装饰器),这个就类似于我们上面高阶函数第二点写过的 run = foo(run),它使得我们不更改函数调用方式的前提下,添加新功能。
  • 这一次,我们既没有修改被装饰函数的源代码逻辑、也没有修改被装饰函数的调用方式。一个简单的装饰器就被我们做出来啦!


写在最后

今天讲解了装饰器的内部实现过程,那么这篇文章就到这里了,期待下回与你们相遇。

see you~