装饰器的定义是给一个对象动态加载功能,像打游戏时给队友上buff一样。一直以来,我对装饰器用的不多,经常会用别的方式搞定,虽然代码丑一点,但也能用。这次遇到一个特别适合装饰器的应用场景,是执行单元测试时的环境配置。
  我是用pytest做单元测试,测试入口都是一个个test_打头的函数。和unittest不一样,pytest中并没有setUp这个方法,虽然有fixture,但读人家的源码时也很少看到有人用,这次遇到问题发现,我靠,是加个装饰器的事,可以把setUp和tearDown一起做了,何必多此一举。
  应用场景用个demo举例,由于生产环境和测试环境的不同,在测试环境中初始化Demo会报错,比如下面这个模块。
  import sys
  class Demo():
  def __init__(self):
  fail()
  def fail():
  if sys.argv[0].split('\')[-1].find('test') > -1:
  raise EnvironmentError(__name__)
  def success():
  print(__name__)
  定义了一个Demo类,初始化时会调用fail函数,这个函数在pytest环境下使用时会raise一个EnvironmentError。解决方案是在这个Demo被调用时将模块中的fail函数替换为success函数。两个单元测试用例如下。
  import demo
  def test_demo():
  try:
  demo.Demo()
  except Exception as e:
  assert isinstance(e, EnvironmentError)
  @replace#替换环境的装饰器
  def test_replace_demo():
  demo.Demo()
  其中,replace是替换环境用的装饰器。装饰器代码如下。
  def replace(fun):#定义装饰器,传入函数名
  _ = demo.fail#保存模块中的fail,以便后面恢复
  demo.fail = demo.success#更改
  def inner():#闭包函数
  fun()
  demo.fail = _#恢复模块中fail函数
  return inner()
  用装饰器配置单元测试环境真是优雅无比,写完后顿时腰不酸背不痛了呢。。。
  装饰器的难点之一是闭包(closure),闭包这个翻译在中文里没有很形象的对应关系,这造成了理解障碍。这种例子挺多的,像单例模式广泛的用途并不是提供一个的“单例”,叫“超级全局宇宙变量”比较好。协程的功能里一点‘协’的作用都没有,叫“想在什么时候挂起在什么适合挂起程”比较好。这样一想,闭包是不是叫“跨作用域包”或“脚踏两条船包”或“闭合环境打包”比较好。
  闭包的一个定义是这样的:
  Closures are functions that refer to independent (free) variables (variables that are used locally, but defined in an enclosing scope). In other words, these functions 'remember' the environment in which they were created.
  闭包的关键能力之一是获取上级作用域,另一个关键点在于python中函数是一个对象,可以传来传去,比如作为返回值。这两点结合起来可以将函数所处的环境打包传到目标区域。在上面一个例子中inner是一个闭包函数,它在这里的作用是将inner函数之前的那个区域,是第二行和第三行,这一环境打包,具体如图所示。

  下面还有个闭包的例子,可以看到在inner外定义了a,b,在inner中使用了a,在test_closure函数实例化后可以看看其__closure__方法中的对象,这个__closure__只包含了a,并没包含b,因为b没有在inner中使用,被垃圾回收了,而a保留了下来,而a是由于闭包的特性保留下来的,可以用pdb来看看__closure__中是否保留了b。
  def test_closure():
  a,b = 1,2
  def inner():
  print(a)
  return inner
  close = test_closure()
  print(close.__closure__[0])#<cell at 0x01CCC4B0: int object at 0x5B975910>
  另外,python中的闭包和javascript中的闭包是一个意思,可能需要实现的功能没js中那么多,但理解python闭包时可以参考js的教程。