step by step, 理解python装饰器

装饰器是什么

装饰器本质上是一个Python函数,它可以让其他函数在不改变代码的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

高阶函数

python的装饰器本质上是一个高阶函数,让我们先来看一下高阶函数是怎么工作的。

python中一切皆对象,函数也不例外,因此可以把函数作为参数传入另外一个函数,这种使用方式就构成了高阶函数。

举个例子。

def foo(x, y, f):
    return f(x) + f(y)

函数foo的参数f,也是一个函数对象,在函数foo中调用了函数f。

用高阶函数扩展原函数的功能

用一个示例来说明。

# 不使用语法糖,为函数增加额外的功能
def deco(func):
    print("before func() called.")
    func()
    print("after func() called.")
 

def myfunc():
    print(" myfunc() called.")
 

deco(myfunc)


$ python test.py
before func() called.
myfunc() called.
after func() called.

改变原函数名指向的函数对象

通过使用高阶函数,我们为原来的函数增加了额外的功能。上面的例子中每次都要调用两个函数,有没有更简捷的方式呢?

我们来看一个演化的例子。

# 不使用语法糖,为函数增加额外的功能
def deco(func):
    def wrap():
        print("before func() called.")
        func()
        print("after func() called.")
    return wrap


def myfunc():
    print("myfunc() called.")


myfunc = deco(myfunc)
myfunc()

$ python test.py
before func() called.
myfunc() called.
after func() called.

上面的代码实现了相同的功能,把标识名myfunc修改为deco(myfunc)后,以后直接用原函数名使用就可以了。但是,标识符myfunc指向的函数对象其实已经发生了变化,我们可以把函数对象的id打印出来,看看这种变化。

# 不使用语法糖,为函数增加额外的功能
def deco(func):
    def wrap():
        print("before func() called.")
        func()
        print("after func() called.")
    print 'wrap', id(wrap)
    return wrap


def myfunc():
    print("myfunc() called.")

print 'before deco', id(myfunc)
myfunc = deco(myfunc)
print 'after deco', id(myfunc)
#myfunc()

$ python test.py
before deco 139931853084264
wrap 139931853084384
after deco 139931853084384

可以看到,执行deco之后,原函数已经变为wrap函数对象。以后每次调用myfunc函数,其实是在调用wrap函数,并且wrap函数内可以调用原myfunc指向的函数。

注意,wrap函数对象是在执行myfunc = deco(myfunc)时生成的,如果有其他代码执行foo = deco(foo),则生成另外一个wrap函数对象。

使用语法糖@


def deco(func):
    def wrap():
        print("before func() called.")
        func()
        print("after func() called.")
    return wrap

@deco
def myfunc():
    print("myfunc() called.")

myfunc()
myfunc()
# print myfunc.__name__
# print myfunc.func_name

$ python test.py
before func() called.
myfunc() called.
after func() called.
before func() called.
myfunc() called.
after func() called.

使用语法糖@装饰函数,实质上就是执行了一次myfunc = deco(myfunc)

被包装函数带参数


def deco(func):
    def wrap(*args, **kwargs):
        print("before func() called.")
        func(*args, **kwargs)
        print("after func() called.")
    return wrap

@deco
def myfunc(a, b=7):
    print 'a + b = %s' % (a + b)
    print("myfunc() called.")

myfunc(2, 5)

$ python test.py
before func() called.
a + b = 7
myfunc() called.
after func() called.

如果被装饰函数有多个参数,应该怎么办呢?可以用位置参数和关键字参数的形式把原函数参数传到包装函数里。

myfunc被包装后,实际上指向wrap函数对象。因此,myfunc的参数应被传递到wrap,wrap应该具有相应的参数列表。但由于装饰器可以修改多个函数,因此wrap的参数事先是不确定的。我们可以用位置参数和关键字参数的形式表示一切可能的参数,在wrap中执行原函数时,再把位置参数和关键字参数解析出来,就把参数完美地传递给原函数了。

装饰器带参数

TO DO

被装饰函数的属性

使用装饰器后,原函数的标识符已指向包装函数,其函数属性在外观上发生了变化,看下面的例子。

def deco(func):
    def wrap(*args, **kwargs):
        print("before func() called.")
        func(*args, **kwargs)
        print("after func() called.")
    return wrap

def myfunc(a, b=7):
    print 'a + b = %s' % (a + b)
    print("myfunc() called.")


print 'myfunc name:', myfunc.func_name
myfunc = deco(myfunc)
print 'myfunc name:', myfunc.func_name

$ python test.py
myfunc name: myfunc
myfunc name: wrap

可以看到,由于myfunc被装饰后实际指向wrap函数,因此函数名为wrap。如果我们想保留函数名为myfunc,应该怎么做呢?

有几种方法可以做到,一种是在装饰器直接修改wrap的函数名,另一种是使用functools自动为我们完成,还可以利用wrapt第三方包。

# 方法一
def deco(func):
    def wrap(*args, **kwargs):
        print("before func() called.")
        func(*args, **kwargs)
        print("after func() called.")
    wrap.func_name = func.func_name
    return wrap

def myfunc(a, b=7):
    print 'a + b = %s' % (a + b)
    print("myfunc() called.")


print 'myfunc name:', myfunc.func_name
myfunc = deco(myfunc)
print 'myfunc name:', myfunc.func_name


# 方法二
import functools

def deco(func):
    @functools.wraps(func)
    def wrap(*args, **kwargs):
        print("before func() called.")
        func(*args, **kwargs)
        print("after func() called.")
    return wrap

def myfunc(a, b=7):
    print 'a + b = %s' % (a + b)
    print("myfunc() called.")


print 'myfunc name:', myfunc.func_name
myfunc = deco(myfunc)
print 'myfunc name:', myfunc.func_name

$ python test.py
myfunc name: myfunc
myfunc name: myfunc

使用wrapt简化装饰器的实现

嵌套的装饰器函数很不直观,我们可以用第三方包简化装饰器的实现,使代码可读性更好。

wrapt是一个功能非常完善的包,可用于实现各种你想到或没想到的装饰器。使用wrapt实现的装饰器解决之前讨论的函数属性问题,甚至inspect.getsource(func)也准确无误。

我们通过示例来学习一下用wrapt实现装饰器。

# 不带参数的装饰器
import wrapt

@wrapt.decorator
def logging(wrapped, instance, args, kwargs):
    print '[DEBUG]: enter {}()'.format(wrapped.__name__)
    return wrapped(*args, **kwargs)

@logging
def myfunc(a, b=7):
    print 'a + b = %s' % (a + b)
    print("myfunc() called.")


myfunc(3)


# 带参数的装饰器
import wrapt

def logging(level):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        print '[DEBUG]: enter {}()'.format(wrapped.__name__)
        return wrapped(*args, **kwargs)
    return wrapper


@logging(level='INFO')
def myfunc(a, b=7):
    print 'a + b = %s' % (a + b)
    print("myfunc() called.")


myfunc(3)

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值