装饰器
装饰器 = 高阶函数 + 嵌套函数
定义
用来装饰其他函数的,即为其他函数添加特定功能的函数,即装饰器本质也是一个函
装饰器函数的两个基本原则
- 装饰器不能修改被装饰函数的源码
- 装饰器不能修改被装饰函数的调用方式
函数即变量
函数即变量的思想:即函数也可以像变量一样赋值使用,如
def foo(): print('in foo') a = foo # 把函数赋值给一个变量 a() # 调用这个变量,即和调用函数一样的效果
高阶函数
高阶函数,符合下列条件之一的函数就是高阶函数:
(1)接收一个函数作为形参
(2)返回一个函数(return 函数)
高阶函数的两个条件对编写装饰器的意义:
(1)接收函数作为形参 -> 可以不改变被装饰函数的代码的前提下增加功能
def foo():
print('in foo')
# gf 函数接收一个函数作为变量,故 gf 是一个高阶函数
def gf(func):
# func = foo
# 等同于把一个函数赋值给一个变量 func,然后通过变量 func 调用函数
func() # 通过变量调用函数
# 高阶函数除了可以调用 foo 函数,即执行 foo 函数功能之外,可以附加执行一些功能,如打印函数地址
# 这接近于装饰器的功能:在不改变别的函数源码的前提下给其添加附加功能
# 但高阶函数不是装饰器,因为满足条件一:不改变被装饰函数的源码,但不满足条件二:不改变被装饰函数的调用方式
print(func)
gf(foo) # 改变了函数 foo 的调用方式
(2)返回一个函数(return 函数) -> 可以不改变被装饰函数的调用方式
def foo():
print('in foo')
# gf 函数接收一个函数作为变量,故 gf 是一个高阶函数
def gf(func):
# func = foo
# 等同于把一个函数赋值给一个变量 func,然后通过变量 func 调用函数
return func
# 可以不改变 foo 函数的调用方式
foo = gf(foo)
foo()
但是可以看出高阶函数不能同时满足两个条件,所以高阶函数不是真正的装饰器
嵌套函数
通过 def 关键字定义在另一个函数中的函数叫嵌套函数,如
def foo():
print('in foo')
def boo():
print('in boo')
装饰器实现
装饰器 = 高阶函数 + 嵌套函数,所以可以得到如下代码
import time
def timer(func):
# func = foo
# 接收一个函数,可以在不改变函数源码的情况下为其增加附加功能
def gf():
start_time = time.time()
func() # => foo()
end_time = time.time()
print('func 运行时间为:', end_time - start_time)
# 返回一个函数,可以不改变被嵌套函数的调用方式
return gf
def foo():
time.sleep(2)
print('in foo')
foo = timer(foo)
foo() # 函数调用方式不变,又多了打印其运行时间的附加功能
@的形式是Python 提供的装饰器的语法糖,等价于 foo = timer(foo)
import time
def timer(func):
# func = foo
# 接收一个函数,可以在不改变函数源码的情况下为其增加附加功能
def gf():
start_time = time.time()
func() # => foo()
end_time = time.time()
print('func 运行时间为:', end_time - start_time)
# 返回一个函数,可以不改变被嵌套函数的调用方式
return gf
@timer # => foo = timer(foo)
def foo():
time.sleep(2)
print('in foo')
# foo = timer(foo)
foo() # 函数调用方式不变,又多了打印其运行时间的附加功能
装饰器的运行过程
可以通过给上述的每一行代码加上断点,运行 debug 就可以看到装饰器的运行过程
装饰器编写的基本套路
- 定义一个接收函数的高阶函数
- 在高阶函数中定义一个嵌套函数,在该嵌套函数中:
- 封装想要添加的功能代码
- 调用作为参数传入的函数名
- 高阶函数返回该嵌套函数
常见的几种装饰器类型
被装饰函数带参数
带一个参数
import time
def timer(func):
# func = foo
# 接收一个函数,可以在不改变函数源码的情况下为其增加附加功能
def gf(name):
start_time = time.time()
func(name) # => foo(name)
end_time = time.time()
print('func 运行时间为:', end_time - start_time)
# 返回一个函数,可以不改变被嵌套函数的调用方式
return gf
@timer # => foo = timer(foo) => foo = gf
def foo(name):
time.sleep(2)
print('in foo', name)
# foo = timer(foo) # => foo = gf
foo('python') # => gf('python')
带多个参数
import time
def timer(func):
# func = foo
# 接收一个函数,可以在不改变函数源码的情况下为其增加附加功能
# *args, **kwargs 可以接收任意的参数
def gf(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs) # => foo(*args, **kwargs)
end_time = time.time()
print('func 运行时间为:', end_time - start_time)
# 返回一个函数,可以不改变被嵌套函数的调用方式
return gf
@timer # => foo = timer(foo) => foo = gf
def foo(name, age):
time.sleep(2)
print('in foo', name, age)
# foo = timer(foo) # => foo = gf
foo('python', 18) # => gf('python', 18)
装饰器本身带参数
import time
def timer(timer_type):
print(timer_type)
def outer(func):
# func = foo
# *args, **kwargs 可以接收任意的参数
def gf(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs) # => foo(*args, **kwargs)
end_time = time.time()
print('func 运行时间为:', end_time - start_time)
# 返回一个函数,可以不改变被嵌套函数的调用方式
return gf
return outer
# 此时 timer('装饰器参数') 带有小括号,表示已经调用了 timer 函数,那么得到了 timer 函数的返回值 outer
# 所以 @timer('装饰器参数') 已经变成了 @outer
@timer('装饰器参数') # => @outer
def foo(name, age):
time.sleep(2)
print('in foo', name, age)
# foo = timer('装饰器参数')(foo) # => foo = outer(foo) # => foo = gf
foo('python', 18) # => gf('python', 18)
# 提示:把每一行代码打断点调试可以更好的理解装饰器的运行过程
被装饰函数带返回值
import time
def timer(timer_type):
print(timer_type)
def outer(func):
# func = foo
# *args, **kwargs 可以接收任意的参数
def gf(*args, **kwargs):
start_time = time.time()
res = func(*args, **kwargs) # => foo(*args, **kwargs)
end_time = time.time()
print('func 运行时间为:', end_time - start_time)
return res
# 返回一个函数,可以不改变被嵌套函数的调用方式
return gf
return outer
# 此时 timer('装饰器参数') 带有小括号,表示已经调用了 timer 函数,那么得到了 timer 函数的返回值 outer
# 所以 @timer('装饰器参数') 已经变成了 @outer
@timer('装饰器参数') # => @outer
def foo(name, age):
time.sleep(2)
print('in foo', name, age)
return 'foo 返回了'
# foo = timer('装饰器参数')(foo) # => foo = outer(foo) # => foo = gf
res = foo('python', 18) # => gf('python', 18)
print(res)
# 提示:把每一行代码打断点调试可以更好的理解装饰器的运行过程
一个函数使用多个装饰器
# 多个装饰器可以放在一个函数上
def add(func):
def inner1():
print("inner1...")
x = func()
return x + 1
return inner1
def cube(func):
def inner2():
print("inner2...")
x = func()
return x * x * x
return inner2
def square(func):
def innner3():
print("inner3...")
x = func()
return x * x
return innner3
@add
@cube
@square
def test():
return 2
# 从结果判断执行顺序:square -> cube -> add
# test 函数传递给 square 函数
# square 函数中的 innner3 函数传递给 cube 函数
# cube 函数中的 innner2 传递给 add 函数
# add 函数中的 innner1 传递给 test
print(test()) # 65