zoukankan      html  css  js  c++  java
  • Python_日记 装饰器和语法糖



    语法糖(Syntactic sugar),是由Peter J. Landin(和图灵一样的天才人物,是他最先发现了Lambda演算,由此而创立了函数式编程)创造的一个词语,它意指那些没有给计算机语言添加新功能,而只是对人类来说更“甜蜜”的语法。语法糖往往给程序员提供了更实用的编码方式,有益于更好的编码风格,更易读。不过其并没有给语言添加什么新东西。

    举个例子:在C语言里用a[i]表示*(a+i),用a[i][j]表示*(*(a+i)+j),看来语法糖不是“现代语言”才有啊,连我们的老大哥C也有,而且这种写法简洁明了,也更好懂了。

    实际上从面向过程到面向对象也是一种语法糖啊,C语言可以通过它的指针、类型转换,结构实现面向对象的编程风格,但是C++更进一步的推广了这种风格,更好用了,不过到了C#把OO的风格发挥得淋漓尽致。OO的编程风格对于面向过程来说是不是一种语法糖呢?如果生硬地照此理解,只有计算机硬件指令才不算语法糖,而其他一切利用编译器、汇编器将代码抽象,和自然语言更相近的手段都算语法糖。

    现在很多很多编程思想,编程理论层出不穷,当然,对于学习来说我们是要抓住技术的核心,但对于工程来说如何提高工程质量,如何提高工程效率也是我们要关注的,既然这些语法糖能辅助我们以更好的方式编写代码为什么要“抵制“呢?

    我想语法糖和其他编程思想一样重要,什么duck type,人本接口,最小接口,约定优于配置,其实不都是一些思想上的“语法糖“?

    不过也并不是没有反对的声音,这其中就有图灵奖的第一个获得者: Alan Perlis。.net从2.0到3.0到3.5底层CLR并没有做更新,没有添加任何新的IL指令,所以C#从2.0到3.0中所有的特性都是语法糖,就算从1.0到2.0,也只有一个泛型不是语法糖,其余的新特性也都是如此,但是每一次C#发布新特性的时候我们不得不为之而鼓舞,为之而喝彩。新的语法可以酿造更好的编程风格,以前一些难以实现的方面现在也轻而易举了。

    需要声明的是“语法糖”这个词绝非贬义词,它可以给我带来方便,是一种便捷的写法,编译器会帮我们做转换;而且可以提高开发编码的效率,在性能上也不会带来损失。
     

    什么是高阶函数?

           -- 把函数名当做参数传给另外一个函数,在另外一个函数中通过参数调用执行

     
    #!/usr/bin/python3
     
     
    def func_x(x):
        return x * 2
     
     
    def func_y(y):
        return y * 3
     
     
    def func_z(x, y):
        # 等价于 return func_x(5) + func_y(3)
        return x(5) + y(3)
     
    if __name__ == '__main__':
        # 把函数当做参数,本质上是把函数的内存地址当做参数传递过去,
        result = func_z(func_x, func_y)
        print(result)

    什么是装饰器?

      -- 在不改变源代码的基础上扩展新需求,装饰器本身也是函数,应用高阶函数实现

      -- 把被装饰的函数内存地址当参数传入装饰器函数体,通过参数调用被装饰的函数

           装饰器原则:

        -- 不改变源代码                         - 因为函数可能在其他地方各种调用,一改动全身

        -- 不改变原函数调用顺序           - 源代码有自己的逻辑处理

        -- 装饰器又叫做语法糖

    装饰器逻辑上格式?

           - 高阶函数+嵌套函数

        需求:

          给某个函数增加一个计算运行时间功能

    #!/usr/bin/python3
     
    
     
    import time
     
     
    def total_time(func):                              # func = hell_word
        def wrapper():                                 # 等价于hell_word()
            start_time =time.time()
            func()
            end_time = time.time()
            print(end_time - start_time)               # 打印统计时间
        return wrapper
     
     
    # 通过装饰器给hell_word函数装上了统计时间的功能,功能逻辑在装饰器中实现
    @total_time                                     
    def hell_word():
        time.sleep(0.5)
        print('hello word')
         
    if __name__ == '__main__':
        hell_word()

           相当于下面的函数逻辑

    import time

    # 装饰器函数 def total_time(func): def wrapper(): start_time =time.time() func() end_time = time.time() print(end_time - start_time) return wrapper <br> def hell_word(): time.sleep(0.5) print('hello word') if __name__ == '__main__': # 把函数当做参数传入装饰器函数,然后装饰器函数返回包裹函数wrapper地址,执行装饰器函数本质上执行包裹函数wrapper中逻辑 total_time(hell_word)()
     

    假如传入的函数中有参数如何?

      -- 需要在wrapper和func中加入收集参数(*args)或收集字典参数(**kwargs),

      -- warps和func可自定义名字,默认如此命名

    如果原函数有个返回值,该如何?

      -- 如果到func结束 直接在func()前面加return

      -- 到func未结束,可以func()结果赋值给一个变量,res = func(*args,**kwargs)到新增逻辑结束后加上 return res

    需求:

      计算出斐波那契数列中第n个数的值?

      求一个共有10个台阶的楼梯,从下走到上面,一次只能迈出1~3个台阶,并且不能后退,有多少中方法?

      要求:

         通过装饰器实现剪枝函数

    如何逻辑整理这个需求? 

      斐波那契数列(黄金分割数列),从数列的第3项开始,每一项都等于前两项之和

      每次迈出都是 1~3 个台阶,剩下就是 7~9 个台阶

                   如果迈出1个台阶,需要求出后面9个台阶的走法

                   如果迈出2个台阶,需要求出后面8个台阶的走法

                   如果迈出3个台阶,需要求出后面7个台阶的走法

            此3种方式走法,通过递归方式实现,递归像树,每次递归都生成子节点函数

     
    def jian_zhi(func):
        # 中间字典,判断已经是否求解过
        median = {}
         
        def wrapper(*args):
            # 假如不在中间字典中,说明没有求解过,添加到字典中去,在的话,直接返回, 将不在递归下去,保证每次递归的唯一性
            if args not in median:
                median[args] = func(*args)
            return median[args]
         
        return wrapper
     
     
    @jian_zhi
    def fibonacci(n):
        if n <= 1:
            return 1
        return fibonacci(n - 1) + fibonacci(n - 2)
     
     
    @jian_zhi
    def climb(n, steps):
        count = 0
        # 当最后台阶为0的时候,说明最后只是走了一次
        if n == 0:
            count = 1
        # 当最后台阶不为0的时候,说明还需要走至少一次
        elif n > 0:
            # 对三种情况进行分别处理momo
            for step in steps:
                count += climb(n - step, steps)
         
        # 返回每次递归的计数
        return count
     
     
    if __name__ == '__main__':
        print(climb(10, (1, 2, 3)))
        print(fibonacci(20))

    需求:

      实现在装饰器函数中,保留 被装饰函数 的元数据

    那,什么是函数的元数据?

           在函数对象中保存着一些函数的元数据,如:

                  f.__name__           函数名

                  f.__doc__              函数文档

                  f.__moudle__       函数所属模块名

                  f.__dict__              属性字典

                  f.__defaults__       默认参数组

                  ……

           在使用装饰器后,在装饰器里访问以上属性时,我们看到的是装饰器函数的元数据

    那,如何解决这个需求?

      通过 functools中的wraps或update_wrapper方法实现,其中每个方法都可单独实现

     

     

    import time
    from functools import (wraps, update_wrapper, WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES)
     
     
    def count_time(func):
        """
        给目标函数加上计算运行时间统计
        """
         
        # 这个装上器和update_wrapper一样,默认参数WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES
        @wraps(func)
        def wrapper(*args, **kwargs):
            start_time = time.time()
             
            # 定义result接收函数返回值,并且在装饰函数最后返回回去
            resutl = func(*args, **kwargs)
            print('运行时间:', time.time() - start_time)
            return resutl
         
        # 其中默认参数 WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES
        # update_wrapper(wrapper, func)
        return wrapper
     
     
    @count_time
    def add(num=100):
        """
        计算 0~num 累加值,默认num=100
        """
        time.sleep(1)
        return sum([x for x in range(num + 1)])
     
     
    if __name__ == '__main__':
        print('函数名:', add.__name__)
        print('属性字典:', add.__dict__)
        print('函数默认参数:', add.__defaults__)
        print('函数所在模块:', add.__module__)
        print('函数文档:', add.__doc__)
         
        # 打印两个默认参数
        # WRAPPER_ASSIGNMENTS :__module__', '__name__', '__qualname__', '__doc__', '__annotations__
        # WRAPPER_UPDATES:__dict__
        print(WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES)
        result = add()
        print(result)

     

    需求:

      实现一个装饰器,用它来检查被装饰函数的参数类型,装饰器可以通过函数,指明函数参数类型,进行函数调用的时候,传入参数,检测到不匹配时,抛出异常

    那,如何解决这个需求?

      -- 先要获取函数的签名,并且获得装饰器中参数,然后把函数签名和装饰器中参数对应绑定

      -- 把调用函数时候传入的参数和函数签名进行绑定

      -- 把实参和装饰器中定义的数据进行类型比较,不匹配抛出异常

     
     
    from inspect import signature
     
     
    def check_type(*ty_args, **ty_kwargs):
        def out_wrapper(func):
            # 通过signature方法,获取函数形参:name, age, height
            sig = signature(func)
            # 获得装饰器传来的参数, 函数签名与之绑定,字典类型
            bind_types = sig.bind_partial(*ty_args, **ty_kwargs).arguments
            print(bind_types)
             
            def wrapper(*args, **kwargs):
                # 给执行函数中具体的实参进行和形参进行绑定,形成字典的形式
                func_type = sig.bind(*args, **kwargs).arguments.items()
                print(func_type)
                # 循环形参和实参字典的items()形式
                for name, obj in func_type:
                    if name in bind_types:
                        # 判断实参是否是指定类型数据
                        if not isinstance(obj, bind_types[name]):
                            raise TypeError('%s must be %s' % (name, bind_types[name]))
                # 假如函数有返回值,通过此方法返回函数的返回值
                res = func(*args, **kwargs)
                return res
             
            return wrapper
         
        return out_wrapper
     
     
    # 通过装饰器实现对函数参数进行类型检查
    @check_type(str, int, float)
    def func(name, age, height):
        print(name, age, height)
     
     
    if __name__ == '__main__':
        # 正常数据
        func('bei_men', 18, 1.75)
        # 错误数据
        func('bei_men', '18', 1.75)

      

    案例:

           为分析程序内哪些函数执行时间开销较大,我们需定义一个带timeout参数的装饰器

           需求:

        统计被装饰函数的运行时间

        时间大于timeout时,将此次函数调用记录到log日志中

        运行时可以修改timeout的值

    如何解决这个问题?

    1. 定义一个装饰器,计算函数执行时间,并与timeout比较,当大于timeout时候,通过logging模块打印出日志信息
    2. 在包裹函数中添加一个函数,通过这个函数来修改timeout变量
    3. 在python3中用nonlocal来声明嵌套作用域中的变量引用,在python2中可以通过把timeout参数变成列表,通过列表索引来进行改值
       
       
      import time
      import logging
      from random import randint
       
       
      def run_time(timeout):
          """
          定义检查函数运行时间,并打印对应函数运行时间超出设定时间日志,并支持更改timeout
          """
           
          # python2
          # timeout = [timeout]
           
          # 真正包裹函数
          def out_wrapper(func):
              def wrapper(*args, **kwargs):
                  start_time = time.time()
                  result = func(*args, **kwargs)
                  used_time = time.time() - start_time
                   
                  # 对于超出timeout的函数进行日志打印
                  if used_time > timeout:
                      msg = '%s: %s > %s' % (func.__name__, used_time, timeout)
                      logging.warn(msg)
                       
                      # python2
                      # if used_time > timeout[0]:
                      #     msg = '%s: %s > %s' % (func.__name__, used_time, timeout[0])
                      #     logging.warn(msg)
                      # return result
                  return result
               
              # 设置timeout参数值
              def set_timeout(value):
                  # 声明嵌套域变量,可以更改,python2通过把列表形式进行更改
                  nonlocal timeout
                  timeout = value
                   
              # 定义接口
              wrapper.set_timeout = set_timeout
               
              # python2
              # def set_timeout(value):
              #     timeout[0] = value
              # wrapper.set_timeout = set_timeout
               
              return wrapper
           
          return out_wrapper
       
       
      @run_time(1.5)
      def func():
          # 随机有50%的几率程序沉睡1秒
          while randint(0, 1):
              time.sleep(1)
          print('func_run')
       
       
      if __name__ == "__main__":
          for _ in range(10):
              func()
           
          print('_' * 50)
           
          # 更改run_time装饰器中timeout参数
          func.set_timeout(2)
          for _ in range(10):
              func()

        

    如何逻辑整理?

      -- 3层 :一层获得value,二层偷梁换柱,三层逻辑处理

      -- 2层:一层偷梁换柱,二层逻辑处理

  • 相关阅读:
    Linux中-POSIX 线程详解
    sql server 2008如何导入mdf,ldf文件
    div浏览器兼容问题
    桥(Bridge)模式
    JSTL核心标签
    filter中的dispatcher解析
    synchronized探究
    最全设计模式(转载)
    面试题总结
    企业为什么要去竞争?
  • 原文地址:https://www.cnblogs.com/ne-zha/p/7482560.html
Copyright © 2011-2022 走看看