Python闭包与装饰器

2019-02-12  本文已影响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

上一篇 下一篇

猜你喜欢

热点阅读