zoukankan      html  css  js  c++  java
  • Python-装饰器(语法糖)上下五千年和前世今生

      装饰器上下五千年和前世今生,这里我们始终要问,装饰器为何产生?装饰器产生解决了什么问题?什么样的需求推动了装饰器的产生?思考问题的时候,始终要问,为什么要这样,而不是那样或者其他样。这里我不先说,也不直接把装饰器的最终样子摆出来,而是说说装饰器发展过程,从这些过程中知道,不是技术推动技术的发展,而是解决这个需求推动技术的产生。接下来一步步构建装饰器产生的过程,从最原始的方向来到最新的状态来解说装饰器为何产生,装饰器产生的过程是如何演变的。

    下面是一段简代码,实现的功能是暂停1秒,然后再打印一句问好"Hai, 北门吹雪"

    import time
    
    
    def bei_men_chui_xue():
        time.sleep(1)
        print("Hai, 北门吹雪")
    
    
    bei_men_chui_xue()
    

    这里需求来了,谁谁谁,总是在提需求,杀了祭天。

    哎呀,在这个功能上在添加一个小小的功能,就是一个小小功能,输出一下这个功能的运行时间,不难吧?

    默默的掏出身后的板砖,啪的一下拍在桌子上,昨天晚上你不是这样说的,说好不改需求的,今天早上就翻脸了?

    好吧,我默默的去改需求了,提交了方案 1

    # 这里通过直接在功能函数bei_men_chui_xue前面添加一个获取开始时间的时间戳,然后再函数bei_men_chui_xue后面获取当前时间戳减去时间的时间戳,得到函数bei_men_chui_xue的运行时间,简单粗暴,嵌入了代码逻辑

    import time
    
    
    def bei_men_chui_xue():
        time.sleep(1)
        print("Hai, 北门吹雪")
    
    
    start_time = time.time()
    bei_men_chui_xue()
    print("run time:", time.time() - start_time)
    

    虽然实现了这个需求,但是这个直接嵌入代码逻辑,把功能代码逻辑包围了起来,看起来不够优雅,改得好看点好么?

    好吧,我又默默器去修改需求了,提交了方案2

    # 这里我把获取函数bei_men_chui_xue运行时间功能封装成函数get_run_time,把函数bei_men_chui_xue对象当作参数传入函数get_run_time中,然后在函数get_run_time运行并统计这个函数的运行时间,这个已经是非常优雅的解决方案,但还是要改变源代码

    import time
    
    
    def bei_men_chui_xue():
        time.sleep(1)
        print("Hai, 北门吹雪")
    
    
    def get_run_time(func):
        start_time = time.time()
        func()
        end_time = time.time()
        print(end_time - start_time)
    
    
    # 高阶函数,已经很优雅的解决方案
    get_run_time(bei_men_chui_xue)
    

    很好,使用了高阶函数,居然知道Python中一切皆对象的原理,把获取时间的功能封装成一个函数,试试用闭包去实现?

    好吧,我又默默器去修改需求了,提交了方案3

    # 这里使用的函数闭包去解决这个需求,闭包最大特性函数中嵌套函数,保留上层函数的变量,其实本质上上和方案2没有区别

    import time
    
    
    def bei_men_chui_xue():
        time.sleep(1)
        print("Hai, 北门吹雪")
    
    
    def get_run_time(func):
        def wrapper():
            start_time = time.time()
            func()
            end_time = time.time()
            print(end_time - start_time)
        return wrapper
    
    
    # 闭包解决方案,看起来方案2远比这个优雅
    f = get_run_time(bei_men_chui_xue)
    f()
    

    嗯,非常好,我也觉得方案2好,但是你修改了源码,改变了源码的执行逻辑,尝试不改变源码逻辑?

    好吧,我又默默器去修改需求了,提交了方案4

    # 这里通过@语法糖符号,直接在函数上添加这个语法糖,给函数添加上以前未有的功能,不改变源代码执行逻辑顺序,是个比方案2更加优秀的方案,其实本质上和方案3没有区别,也是利用闭包函数的特性,然后在语法上进行规范,抽象为@,就像装饰在函数上,语法糖又被称为装饰器,用函数get_run_time去装饰函数bei_men_chui_xue添加上统计运行时间的功能,执行原函数bei_men_chui_xue本质上被置换为执行wrapper函数,通过闭包特性保留上层函数的变量。

    import time
    
    
    def get_run_time(func):
        def wrapper():
            start_time = time.time()
            func()
            end_time = time.time()
            print(end_time - start_time)
        return wrapper
    
    
    @get_run_time
    def bei_men_chui_xue():
        time.sleep(1)
        print("Hai, 北门吹雪")
    
    
    # 装饰器解决方案,前面的语法糖才是装饰器核心
    bei_men_chui_xue()
    

    不错不错,装饰器把被装饰函数传递进装饰器,调用源函数其实本质上调用装饰器中的wrapper函数,我想在原函数传递进去参数,如何?

    好吧,我又默默器去修改需求了,提交了方案5

    # 执行原函数bei_men_chui_xue其实本质上执行 wrapper函数,在wrapper函数中保存原函数func的执行结果,最后返回回去

    import time
    
    
    def get_run_time(func):
        def wrapper(*args, **kwargs):
            start_time = time.time()
            func(*args, **kwargs)
            end_time = time.time()
            print(end_time - start_time)
        return wrapper
    
    
    @get_run_time
    def bei_men_chui_xue(name):
        time.sleep(1)
        print("Hai, %s" % name)
    
    
    # 解决装饰的函数有变量
    bei_men_chui_xue("北门吹雪")
    

    很好,很好,通过往装饰器中传递 *args **kwargs参数完成向原函数传递参数,我想获得一下原函数的返回值?如被装饰函数的返回值?

    好吧,我又默默器去修改需求了,提交了方案6

    # 原函数有多个参数,我不关心参数到底是什么,只需要wrapper接收 *args **kwargs ,真正函数 func也接收这两个参数

    import time
    
    
    def get_run_time(func):
        def wrapper(*args, **kwargs):
            start_time = time.time()
            r = func(*args, **kwargs)
            end_time = time.time()
            run_time = end_time - start_time
            print(run_time)
            # 被装饰函数返回值
            return r
        return wrapper
    
    
    @get_run_time
    def bei_men_chui_xue(name):
        time.sleep(1)
        return "Hai, %s" % name
    
    
    # 解决装饰的函数有变量
    r = bei_men_chui_xue("北门吹雪")
    print(r)
    

    功能还需要改动,我不想输出其运行时间,可以通过向装饰器中传入参数,如果运行时间超过这个数打印已经超时?

    好吧,我又默默器去修改需求了,提交了方案7

    # 本质上还是通过函数闭包特性保存上层函数变量

    import time
    
    
    def get_run_time(time_out):
        def out_wrapper(func):
            def wrapper(*args, **kwargs):
                start_time = time.time()
                r = func(*args, **kwargs)
                end_time = time.time()
                # 获取运行时间
                run_time = end_time - start_time
                # 检查程序运行时间是否超时
                if run_time > time_out:
                    print("运行时间已经超时")
                # 被装饰函数返回值
                return r
            return wrapper
        return out_wrapper
    
    
    @get_run_time(time_out=1)
    def bei_men_chui_xue(name):
        time.sleep(1)
        return "Hai, %s" % name
    
    
    bei_men_chui_xue("北门吹雪")
    # 获取返回值
    r = bei_men_chui_xue("北门吹雪")
    print(r)
    

    完美,这个才是五彩斑斓的黑,加个鸡腿

    从这个过程中可以看出,被装饰函数运行时候其实运行的是装饰器内部wrapper函数,通过函数闭包实现对一些参数状态的保存,从而实现各种需求的装饰器,装饰器可以优雅解决这些问题。

  • 相关阅读:
    专职DBA-MySQL体系结构与基本管理
    JSON
    MIME类型
    文件上传下载
    response常用的方法
    2020.11.27小记
    HTTP请求状态码
    1561. Maximum Number of Coins You Can Get
    1558. Minimum Numbers of Function Calls to Make Target Array
    1557. Minimum Number of Vertices to Reach All Nodes
  • 原文地址:https://www.cnblogs.com/2bjiujiu/p/9135290.html
Copyright © 2011-2022 走看看