美文网首页
Python闭包与装饰器

Python闭包与装饰器

作者: 马本不想再等了 | 来源:发表于2019-02-12 23:49 被阅读0次

    1. 闭包

    概念:在函数嵌套的前提下,内层函数引用了外层函数的变量(包括参数),外层函数又把内层函数当做返回值进行返回。这个内层函数+多引用的外层变量,称为‘闭包’。
    实例1:

    def test1(a):
      b=10
      def test2()
        print(a)
        print(b)
      return test2
    

    应用场景:外层函数,根据不同的参数生成不同作用功能的函数。
    注意事项:

    1. 闭包中,如果要修改引用的外层变量,则需要使用nonlocal变量声明,否则会当做是闭包内,新定义的变量。
    2. 当闭包内,引用了一个后期会发生变化的变量时,一定要注意。
      实例2:
    def test()
      funcs = []
      for i in range(1, 4):
        def test2():
          print(i)
        funcs.append(test2)
      return funcs
    newfuncs = test()
    
    newfuncs[0]()
    newfuncs[1]()
    newfuncs[2]()
    
    >>>3
    >>>3
    >>>3
    

    以上情况的出现是因为,虽然在最后newfuncs中调用不同的test2函数,但在此之前funcs函数内的test2没有被执行,而test2函数已经被定义了3次了,其中range(1, 4)也已经走了3次了,相应的i也已经从1走到3了,所以在最后调用test2的,会去取i的值,此时i=3,故三次调用都会返回3。
    案例2改进:

    def test():
      funcs = []
      for i in range(1, 4):
        def test2(num):
          def inner():
            print(num)
          return inner
        funcs.append(test2=(i))
      return funcs
    newfuncs = test()
    
    newfuncs[0]()
    newfuncs[1]()
    newfuncs[2]()
    
    >>>1
    >>>2
    >>>3
    

    解释:在改进方案中,其实就是给之前的test2内再嵌套了一个函数inner,inner负责之前test2的功能,然后每次test2加入funcs列表的时,直接把的i传入test2,同时又不执行inner函数,之后newfuncs调用test2时,其中已经包含的每次不同的i的值,inner函数执行输出的结果也就会不同。

    2. 装饰器

    2.1 概念理解

    装饰器本质上是一个函数,它可以让其他函数在不需要做任何改动的前提下增加额外的功能,装饰器的返回值也是一个函数对象。
    装饰器经常用于有切面需求的场景:插入日志、性能测试、事务处理、缓存、权限校验等场景。
    分步理解装饰器:
    (1). 需要给一个函数a()添加一个功能,功能写在decorator()内,当我们调用'decrotor(a)`的时候就会在执行a()之前执行我们要的功能。

    def a():
      print('i am a')
    
    def decorator(a):
      print('新添加的功能')
      a()
    

    (2).我们需要该函数a()在业务逻辑代码中调用的时候名称还是a()(函数的单一职责性),而不是decrotor(a),那么最简单的方法就是在把它赋值给a,即a = decorator(a)
    (3). 但是在进行a = decorator(a)时,右边的decorator(a)函数会被直接执行,而此时逻辑代码还没有调用它,为避免该情况发生,我们使用闭包的思想。
    (4). 这里使用闭包思想:也就是decorator(a)返回一个函数(后面我们会把这个函数写成warpper),将这个函数赋值给a,这样在进行a = decorator(a)时右边就不会被立即执行了。最后我们把需要添加的新功能,以及a()都写在warpper函数里,这样写就可以使得只有真正在业务逻辑代码中调用该函数的时候,warpper才会被执行,同时我们会加上一句a = decorator(a),来确保业务逻辑代码中调用的时候名称还是a()。所以写出的结果如下:

    def a():
      print('i am a')
    
    def decorator(a):
      def warpper():
        print('新添加的功能')
        a()
      return warpper
    
    a = decorator(a)
    a()
    # 以上写法中
    decorator(a) 等价于
    def warpper():
        print('新添加的功能')
        a()
    

    最后 Python给我们给定了以个语法糖@,即可以用@decorator 代替a = decorator(a),这也就演变出装饰器最终的写法:

    def decorator(a):
      def warpper():
        print('新添加的功能')
        a()
      return warpper
    
    @decorator
    def a():
      print('i am a')
    
    a()
    
    >>>新添加的功能
    >>>i am a
    

    装饰器采用了闭包的思想,在装饰函数的同时不执行函数,只有到正正的业务逻辑代码调用的时候再执行函数。

    2.2 装饰器的执行时间

    当@decorator出现的时候,decorator装饰器函数就立即被执行了。

    2.3 装饰器的执行顺序

    从上到下去装饰,从下到上去执行。

    @a
    @b
    @c
    def func():
    # 等效于
    func = a(b(c(func)))
    

    2.4 对有参数的函数进行装饰

    如果需要被装饰的的函数a()中有参数呢?这时候就需要有一个东西来接收函数a()中的参数,而函数a()中的参数也可能是多种多样的,这时候我们可以这样来写warpper函数:warpper(*args, **kwargs),但是在warpper函数中调用的的func函数也需要用:func(**args, **kwargs)的写法来解包warpper中接收的函数,这样func才可以正常执行。代码如下:

    def decorator(func):
      def warpper(*args, **kwargs):
        print('新添加的功能')
        func(*args, **kwargs)
      return warpper
    
    @decorator
    def a(i='我', am='是',):
      print(i, am, "a")
      
    a()
    >>>新添加的功能
    >>>我 是 a
    

    2.5 对有返回值的函数进行装饰

    如果需要被装饰的的函数a()中有return返回值呢?这时候就需要有一个东西来返回函数a()中的返回值,所以我们在warpper中也应该写入return的部分,代码如下:

    def decorator(func):
      def warpper(*args, **kwargs):
        print('新添加的功能')
        return func(*args, **kwargs)
      return warpper
    
    @decorator
    def a(i='我', am='是',):
      print(i, am, "a")
      b = i+am+"b"
      return b
    
    b = a()
    print(b)
    >>>新添加的功能
    >>>我 是 a
    >>>我是b
    

    原则上要保证,装饰器中的warpper函数的格式和被装饰的函数格式一致。

    2.6 带有参数的装饰器

    如果我们可以给装饰器本身传入一个参数,那么装饰器就可以随着传入参数的变化而做出不同的装饰功能,例如:print('新添加的功能')、print('新添加的技术')、print('新添加的颜色')。那么,该如何实现带有参数的装饰器呢? 思路:我们定义个一个外函数,把装饰器放入其中,然后装饰器中可以使用这个外函数传入的值,同时在外函数的最后返回其中的装饰器,那么这个整体就可以成为一个新的带有参数的装饰器。代码如下:

    def zhuangshiqi(i):
      def decorator(func):
        def warpper(*args, **kwargs):
          print('新添加的'+i)
          return func(*args, **kwargs)
        return warpper
      return decorator
    
    @zhuangshiqi(i="颜色而不是功能")
    def a(i='我', am='是'):
      print(i, am, "a")
      return a
    
    a()
    >>>新添加的颜色而不是功能
    >>>我 是 a
    

    实例1:简单的装饰器

    def decorator(func):
      def wrapper(*args, *kwargs):
        logging.warn("%s is running" %  func.__name__)
        return func(*args, *kwargs)
      return wrapper
    
    @decorator
    def a():
      print('i am a')
    
    a()
    
    >>> a is running
    >>> i am a
    

    实例2:装饰器用于验证权限的例子

    userAge = 40
    
    def canYou(func):
      def decorator(*args, **kwargs):
          if userAge > 1 and userAge < 10:
              return func(*args, **kwargs)
          print('你的年龄不符合要求,不能看')
      return decorator
    
    @canYou
    def play():
      print('开始播放动画片 《喜洋洋和灰太狼》')
    
    play()
    
    >>> 你的年龄不符合要求,不能看
    

    内置装饰器

    @staticmathod 、@classmethod 、@property

    相关文章

      网友评论

          本文标题:Python闭包与装饰器

          本文链接:https://www.haomeiwen.com/subject/zppleqtx.html