python装饰器

作为python中一个比较重要也比较高级的语法功能,关于装饰器的博客和文章在网上较多.看过不少相关的博客和书,每一次看完都觉得自己懂了,原来是这样,挺简单的嘛.到实际要写的时候,发现还是忘记的差不多了,也就是没有弄懂.最近写装饰器的时候,遇到一些疑惑,就准备记录下来,以免下次再有同样的疑惑时能够有迹可寻.

需求: 写一个装饰器,装饰器的参数作为字典的键,被装饰函数为字典的值,如:

@deco('/index')
def index():
  ...

maps = {'/index': index}

我的代码:

maps = {}

def deco(path):
  def wrap1(func):
    def wrap2(*args, **kwargs):
        maps[path] = func
      return func
    return wrap2
  return wrap1

@deco('/index')
def index():
  return 'I am index'

print maps 

按照预期,打印maps应该是:

{'/index', <function index at ....>}

结果为空.

为什么?我突然想到flask源码中添加路由的装饰器的写法,先写一个添加函数,再在基础之上写一个装饰器.于是,我就立马动手了.

def add_address(path, func):
  maps[path] = func

def deco(path):
  def wrap(func):
    add_address(path, func)
    return func
  return wrap

这次发现有了.

为什么两种写法结果不一致?第一种写法具有三层结构,第二种写法也是三层结构(deco+wrap+add_address).仔细看了下,发现第二种写法其实只有两层结构,add_address函数被调用了,相当于一个结果.两种方法的差别有了,问题肯定在两者的差别之中.

从装饰器的本质开始,先分析第二种写法.

@deco('/index')
def index():
  ...

等价于

index = deco('/index')(index)

回溯至装饰器中,相当于调用了wrap()

index = wrap(index)

接着:

调用了add_address函数,并返回index函数

没有问题

再看看第一种方法:

@deco('/index')
def index():
  ...

等价于

index = deco('/index')(index)

相当于调用wrap1(index)

index = wrap1(index)

返回的是wrap2,即

index = wrap2

自此,装饰器完成了自己的工作,对于wrap2函数并没有调用.因此,也就没有对maps字典进行添加操作.

对第一种方法进行修改:

(1)去掉wrap2函数,变成第二种方法

def deco(path):
  def wrap1(func):
    maps[path] = func
    return func
  return wrap1 

(2)将maps[path] = func提升一级

def deco(path):
  def wrap1(func):
    maps[path] = func
    def wrap2(*args, **kwargs):
      return func(*args, **kwargs)   # 一定要把(*args, **kwargs)加上,否则在调用index函数时,返回的是index函数,而不是调用index函数的结果
    return wrap2
  
return wrap1

接着我又重头捋了一下装饰器的内容

(1)从一个简单的装饰器开始

def deco(func):
  print 'I am deco'
  return func

@deco
def foo():
  print 'I am foo'

等价于

foo = deco(foo)

运行结果:

I am deco  # 说明装饰器加载时,就已经运行了

 

(2)被装饰函数(如foo函数)有参数

在(1)中的装饰器也是可以用的,因为装饰器装饰之后返回的就是原函数,并没有调用

foo = deco(foo) = foo

对foo函数进行调用时,才会涉及参数

foo(2) = deco(foo)(2) = foo(2)

还有一种写法,就是在包裹一层函数

def deco(func):

  def wrap(*args, **kwarags):

    print 'I am wrap'

    return func(*args, **kwargs)

  return wrap

等价于:

foo = deco(foo) = wrap

调用foo时:

foo(2) = deco(foo)(2) = wrap(2) = func(2) = foo(2)  # 此时才会打印'I am wrap'

在有一层函数包裹时,被装饰函数一定得调用最终才能顺利返回原函数想要的结果(整体上,函数被包裹了几层,就得调用几次)

这两种写法不同点在第一种返回的是原来的函数,第二种函数的函数名改变成了wrap,结果没有改变(可以了解一下functools.wrapsfunctool.update_wrapper)

链接:  https://docs.python.org/2/library/functools.html#functools.update_wrapper

(3)装饰函数自带参数

和前面讨论的问题差不多

 

posted @ 2018-07-25 12:21  静静地挖坑  阅读(172)  评论(0)    收藏  举报