装饰器的难点:在梳理了装饰器的整个内容之后,我认为难点不是装饰器本身,而是直接调用被装饰的函数,让人无法理解背后究竟发生了什么。
一、引出装饰器概念
引入问题: 定义了一个函数,想在运行时动态的增加功能,又不想改动函数本身的代码?
示例:
希望对下列函数调用增加log功能,打印出函数调用:
def f1(x): return x*2
def f2(x): return x*x
def f3(x): return x*x*x
方法一:直接修改原函数的定义
def f1(x):
print("call f1")
return x*2
def f2(x):
print("call f2")
return x*x
def f3(x):
print("call f3")
return x*x*x
方法二:通过高阶函数返回新函数
思考:高阶函数
(1)、接受函数作为参数
(2)、返回函数
(3)、因此:通过接收一个函数,内部对其包装,然后返回一个新函数,这样子动态的增强函数功能
def f1(x): return x*2
def new_fun(f):
""" 装饰器函数 """
def fun(x):
print("call " + f.__name__)
return f(x)
return fun
new_fun(f1)(10)
方法三:真正的装饰器
Python内置的 @ 语法就是为了简化装饰器的使用。
@new_fun代表的意思:调用new_fun函数,参数是@所在行下面的函数
@new_fun
def f1(x): return x*2
@new_fun
def f2(x): return x*x
@new_fun
def f3(x): return x*x*x
f1(10)
f2(100)
f3(1000)
f1(10)的调用方式等价于new_fun(f1)(10), new_fun(f1)返回的是装饰器内部函数fun,即fun(10). 整个功能的完成被分成三部分:装饰器函数执行,返回装饰器内部函数;装饰器内部函数执行,执行logging功能;在执行内部函数的同时或者执行完内部函数之后,执行被装饰函数f(x)。因此我们也容易看出整个装饰器就是三部分:装饰器函数,装饰器内部函数,被装饰函数。
三、装饰器作用:可以极大简化代码,避免每个函数编写重复性的代码
(1)、打印日志:@log,如本文所举的例子
(2)、检测性能:@performance,例如机器学习训练模型,监测多个模型,多个函数运行时间。
四、装饰器函数三步走
(1)、定义自己先要执行的函数
(2)、定义装饰器函数
def new_fun(要执行的函数f):
def fun(要执行的函数的参数x):
添加函数功能
return f(x)
return fun
(3)、装饰器进行修饰
# -*- coding: utf-8 -*-
# 装饰器监测函数的调用和性能
def print_log(f):
""" 装饰器函数: 监测函数的调用 """
def fun(x):
print(f.__name__ + " is called.")
# 可以不返回, 直接在此处执行f(x)
return f(x)
return fun
@print_log
def sort_fun(num_list):
""" 排序函数 """
return sorted(num_list)
if __name__ == "__main__":
num_list = sort_fun(list(range(100))*100)