• 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函数,通过函数闭包实现对一些参数状态的保存,从而实现各种需求的装饰器,装饰器可以优雅解决这些问题。

  • 相关阅读:
    System.ServiceModel.CommunicationException:The underlying connection was closed: The connection was closed unexpectedly
    研究jBPM和NetBPM
    研究:Microsoft Solution Framework
    iOS银联,支付宝,微信,ping++开发文档
    Xampp+Openfire+Spark的简单使用
    ObjectiveC与Swift混编
    网络抓包教程
    iOS版微信开发小结(微信支付,APP跳转微信公众号)
    七牛上传图片问题总结
    2012年之总结
  • 原文地址:https://www.cnblogs.com/2bjiujiu/p/9135290.html
Copyright © 2020-2023  润新知