04_05、装饰器

一、装饰器的概念

  装饰:给函数增加额外的功能
  器:就是工具

  装饰器不是一个新的知识,而是名称空间,闭包函数、函数嵌套等等的组合就是装饰器

  装饰器的核心思想:在"不改变原有函数的代码"和"原有调用方式"的基础上"增加额外"的功能

复制代码
import time  # 引入time模块

# 这是一个index函数
# 现在要给它添加一个功能:给index函数统计执行时间
def index():
    time.sleep(2)  # 运行阻滞2秒,方便更清晰查看函数的执行时间
    print('from index')


start_time = time.time()  # 在函数执行之前打印一个时间节点
index()  # 调用函数,开始运行函数
end_time = time.time()  # 等待函数执行完毕之后,再打印一个时间节点
print(end_time - start_time)  # 总的执行时间就是两个的差值
# 通过这四行代码给函数index添加了一个统计运行时间的“额外功能” # 而且没有改变函数index的”原有代码“和”调用方式“ # 所以这四行代码就可以说是函数index的装饰器 # 麻烦的地方在于这个装饰器被定死了,只能统计这一个函数的运行时间
复制代码

 二、简易版本的装饰器

  在前一段代码中,通过装饰器已经实现了对函数index的运行时间统计。

  但是局限性太大,它只能对index这一个函数统计,不能实现对多个函数的重复利用

  为此,需要对装饰器进行封装优化

  第一种优化方式:

      利用实参向形参传递的方式优化

      注意:此时改变了函数的调用方式,不能再称之为装饰器

复制代码
import time  # 引入time模块


# 这是一个index函数
# 现在要给它添加一个功能:给index函数统计执行时间
def index():
    time.sleep(2)  # 运行阻滞2秒,方便更清晰查看函数的执行时间
    print('from index')


# 这是一个home函数
# 也要给它添加一个统计执行时间得功能
def home():
    time.sleep(3)  # 运行阻滞2秒,方便更清晰查看函数的执行时间
    print('from home')


# 通过对代码封装优化,以函数形式实现统计运行时间的功能
def get_time(func):  # 通过实参向形参传递的方式,实现给多个函数统计运行时间
    start_time = time.time()  # 在函数执行之前打印一个时间节点
    func()  # 调用函数,开始运行函数
    end_time = time.time()  # 等待函数执行完毕之后,再打印一个时间节点
    print(end_time - start_time)  # 总的执行时间就是两个的差值


get_time(index)  # 把要统计的函数index作为实参写入,并调用,得出函数index的运行时间
get_time(home)  # 把要统计的函数home作为实参写入,并调用,得出函数home的运行时间
# 注意这里函数调用方式已经改变,通过get_time调用而不是index或者home本身
复制代码

  第二种优化方式:

    在原有功能函数的外围再加一层函数,实现闭包

    利用闭包函数传参,可以不改变函数原有代码和调用方式

复制代码
import time  # 引入time模块


# 这是一个index函数
# 现在要给它添加一个功能:给index函数统计执行时间
def index():
    time.sleep(2)  # 运行阻滞2秒,方便更清晰查看函数的执行时间
    print('from index')


# 这是一个home函数
# 也要给它添加一个统计执行时间得功能
def home():
    time.sleep(3)  # 运行阻滞2秒,方便更清晰查看函数的执行时间
    print('from home')


def outer(func):  # 写入形参,内部函数调用此参数,完成函数闭包
    def get_time():
        start_time = time.time()
        func()
        end_time = time.time()
        print(end_time - start_time)

    return get_time  # 调用get_time完毕后给出返回值,已进入真正的功能模块


index = outer(index)  # outer(index)得出返回值get_time,为保证不改变调用方式,给get_time赋值index
index()  # index()开始调用,由于index是get_time赋值给的,所以是get_time(),调用get_time,可以直接得出运行时间

home = outer(home)  # outer(home)得出返回值get_time,为保证不改变调用方式,给get_time赋值home
home()  # home()开始调用,由于home是get_time赋值给的,所以是get_time(),调用get_time,可以直接得出运行时间
复制代码

三、进阶版本的装饰器

  有参函数的装饰器

    之前所有的装饰器实例都是无参函数,无需给函数传值

    如果换成有参函数,需要一些变动,否则报错

复制代码
import time  # 引入time模块


# 这是一个有参函数
# 要求统计这个函数的运行时间
def login(name):  # 给函数login一个形参name
    time.sleep(2)
    print('from login', name)


def outer(func):  # 写入形参,内部函数调用此参数,完成函数闭包
    def get_time(name):
        start_time = time.time()
        func(name)
        end_time = time.time()
        print(end_time - start_time)

    return get_time  # 调用get_time完毕后给出返回值,已进入真正的功能模块


login = outer(login)  # 把要统计的函数login的名称作为实参传给闭包函数outer,以实现统计运行时间的功能
# 得到的返回值为get_time,
# 为了保证调用方式相同,把get_time再次赋值给login,此时login就是get_time
login('ly')  # login()开始调用,就是get_time开始调用,运行函数实现统计运行时间的功能
复制代码

  有参和无参函数都可实现的装饰器

    无参函数和有参函数因为参数的不同,代码也需要相应的改变

    但是通过*args和**kwargs可以实现二者兼容

 

复制代码
import time  # 引入time模块


def index():
    time.sleep(3)
    print('这是无参函数index')


# 这是一个有参函数
# 要求统计这个函数的运行时间
def login(name):  # 给函数login一个形参name
    time.sleep(2)
    print('这是有参函数login', name)


def outer(func):  # 写入形参,内部函数调用此参数,完成函数闭包
    def get_time(*args, **kwargs):  # 把形参写成(*args, **kwargs)格式,可以同时满足有参和无参函数
        start_time = time.time()
        func(*args, **kwargs)  # 调用外部函数时,也要写成(*args, **kwargs)格式,此时需要统计时间的函数开始运行
        end_time = time.time()
        print(end_time - start_time)

    return get_time  # 调用get_time完毕后给出返回值,已进入真正的功能模块


# 无参函数的统计结果
index = outer(index)
index()

# 有参函数的统计结果
login = outer(login)  # 把要统计的函数login的名称作为实参传给闭包函数outer,以实现统计运行时间的功能
login('ly')
复制代码

四、装饰器的返回值问题

  函数的返回值return的特性是:

    不写return或者单写return后面不添加值,则返回None

     如果写值则返回该值

  装饰器的核心功能是最内部空间的运行代码,最内部的函数代码的返回值也是装饰器的返回值

  因此不写或单写return,返回的值是None,而不是需要的函数的返回值

  解决方式为:

    在装饰器结尾给调用的函数重新赋值

    在内部空间中return新赋值的变量名

复制代码
import time  # 引入time模块


def index():
    time.sleep(1)
    print('这是无参函数index')
    return '这是无参函数index'


# 这是一个有参函数
# 要求统计这个函数的运行时间
def login(name):  # 给函数login一个形参name
    time.sleep(1)
    print('这是有参函数login', name)
    return '这是有参函数login'


def outer(func):  # 写入形参,内部函数调用此参数,完成函数闭包
    def get_time(*args, **kwargs):  # 把形参写成(*args, **kwargs)格式,可以同时满足有参和无参函数
        start_time = time.time()
        res = func(*args, **kwargs)  # 给调用的函数重新赋值
        end_time = time.time()
        print(end_time - start_time)
        return res  # 函数get_time返回值为需要统计的函数的返回值

    return get_time  # 调用get_time完毕后给出返回值,已进入真正的功能模块


# 无参函数的统计结果
index = outer(index)
res = index()
print(res) 

# 有参函数的统计结果
login = outer(login)  # 把要统计的函数login的名称作为实参传给闭包函数outer,以实现统计运行时间的功能
res = login('ly')
print(res)
复制代码

五、装饰器的固定模板

def outer(func):
    def inner(*args,**kwargs):
        print('函数执行之前要执行的代码')
        res = func(*args,**kwargs)
        print('函数执行之后要执行的代码')
        return res

    return inner

六、装饰器的语法糖

  1、什么是语法糖

    根据之前的实例发现,在装饰器调用阶段

    若要调用装饰器每次都需要把装饰器的函数   outer(被装饰函数名)   重新赋值给被装饰的函数名

    然后再次给要调用的函数名重新赋值,比较麻烦

    函数的语法糖@outer代替了赋值这一功能

    装饰器语法糖@,把它下面的函数当参数传入语法糖,并且把返回结果赋值给函数名

  2、注意:

    @outer必须放在要调用的函数之上,在装饰器之下

    把语法糖下面紧贴着的函数名当成参数传递给装饰器函数参数

  3、有语法糖的装饰器的写入流程:

    装饰器

    语法糖

    要执行的函数

    函数调用

复制代码
import time  # 引入time模块


def outer(func):  # 写入形参,内部函数调用此参数,完成函数闭包
    def get_time(*args, **kwargs):  # 把形参写成(*args, **kwargs)格式,可以同时满足有参和无参函数
        start_time = time.time()
        res = func(*args, **kwargs)  # 给调用的函数重新赋值
        end_time = time.time()
        print(end_time - start_time)
        return res  # 函数get_time返回值为需要统计的函数的返回值

    return get_time  # 调用get_time完毕后给出返回值,已进入真正的功能模块


@outer  # 相当于index = outer(index)
def index():
    time.sleep(1)
    print('这是无参函数index')
    return '这是无参函数index'
res = index()
@outer
# 相当于login = outer(login) def login(name): # 给函数login一个形参name time.sleep(1) print('这是有参函数login', name) return '这是有参函数login'
res = login('ly')
复制代码

七、装饰器的双层语法糖

  1、双层语法糖

    pass

  2、三层语法糖练习

    pass

八、有参装饰器

    pass

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

posted @   三三得九86  阅读(33)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示