修饰器+wraps

装饰器原理

def w1(func):
    def inner():
        print('...验证权限...')
        func()
    return inner
@w1
def f1():
    print('f1 called')

@w1
def f2():
    print('f2 called')
f1()
f2()

首先,开看我们的装饰器函数w1,该函数接收一个参数func,其实就是接收一个方法名,w1内部又定义一个函数inner,在inner函数中增加权限校验,并在验证完权限后调用传进来的参数func,同时w1的返回值为内部函数inner,其实就是一个闭包函数。

然后,再来看一下,在f1上增加@w1,那这是什么意思呢?当python解释器执行到这句话的时候,会去调用w1函数,同时将被装饰的函数名作为参数传入(此时为f1),根据闭包一文分析,在执行w1函数的时候,此时直接把inner函数返回了,同时把它赋值给f1,此时的f1已经不是未加装饰时的f1了,而是指向了w1.inner函数地址。相当于f1=w1(f1)

接下来,在调用f1()的时候,其实调用的是w1.inner函数,那么此时就会先执行权限验证,然后再调用原来的f1(),该处的f1就是通过装饰传进来的参数f1。

这样下来,就完成了对f1的装饰,实现了权限验证。

执行时机

def makeBold(fun):
    print('----a----')

    def inner():
        print('----1----')
        return '<b>' + fun() + '</b>'
    return inner
def makeItalic(fun):
    print('----b----')

    def inner():
        print('----2----')
        return '<i>' + fun() + '</i>'
    return inner
@makeBold
@makeItalic
def test():
    print('----c----')
    print('----3----')
    return 'hello python decorator'
ret = test()
print(ret)
执行结果:
----b----
----a----
----1----
----2----
----c----
----3----
<b><i>hello python decorator</i></b>

函数装饰器

有参数函数进行装饰

def w_add(func):
    def inner(*args, **kwargs):
        print('add inner called')
        func(*args, **kwargs)
    return inner
@w_add
def add(a, b):
    print('%d + %d = %d' % (a, b, a + b))
@w_add
def add2(a, b, c):
    print('%d + %d + %d = %d' % (a, b, c, a + b + c))
add(2, 4)
add2(2, 4, 6)

结果:

add inner called
2 + 4 = 6
add inner called
2 + 4 + 6 = 12

带有返回值的函数进行装饰

def w_test(func):
    def inner():
        print('w_test inner called start')
        str = func()
        print('w_test inner called end')
        return str
    return inner
@w_test
def test():
    print('this is test fun')
    return 'hello'
ret = test()
print('ret value is %s' % ret)

执行结果:

w_test inner called start
this is test fun
w_test inner called end
ret value is hello

带参数的装饰器

def func_args(pre='xiaoqiang'):
    def w_test_log(func):
        def inner():
            print('...记录日志...visitor is %s' % pre)
            func()
        return inner
    return w_test_log
# 带有参数的装饰器能够起到在运行时,有不同的功能
# 先执行func_args('wangcai'),返回w_test_log函数的引用
# @w_test_log
# 使用@w_test_log对test_log进行装饰
@func_args('wangcai')
def test_log():
    print('this is test log')
test_log()

执行结果:

...记录日志...visitor is wangcai
this is test log

类装饰器

装饰器函数其实是一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。
在python中,一般callable对象都是函数,但是也有例外。比如只要某个对象重写了call方法,那么这个对象就是callable的。

class Test(object):
    def __init__(self, func):
        print('test init')
        print('func name is %s ' % func.__name__)
        self.__func = func
    def __call__(self, *args, **kwargs):
        print('装饰器中的功能')
        self.__func()
@Test
def test():
    print('this is test func')
test()

执行结果:

test init
func name is test 
装饰器中的功能
this is test func

wraps修饰器

partial:
首先说partial函数,在官方文档的描述中,这个函数的声明如下:functools.partial(func, *args, **keywords)。它的作用就是返回一个partial对象,当这个partial对象被调用的时候,就像通过func(*args, **kwargs)的形式来调用func函数一样。如果有额外的 位置参数(args) 或者 关键字参数(*kwargs) 被传给了这个partial对象,那它们也都会被传递给func函数,如果一个参数被多次传入,那么后面的值会覆盖前面的值。

from functools import partial

def add(x, y):
    return x+y

# 这里创造了一个新的函数add2,只接受一个整型参数,然后将这个参数统一加上2
add2 = partial(add, y=2)

add2(3)  # 这里将会输出5

update_wrapper:
update_wapper的源码如下:

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                       '__annotations__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):
    for attr in assigned:
        try:
            value = getattr(wrapped, attr)
        except AttributeError:
            pass
        else:
            setattr(wrapper, attr, value)
    for attr in updated:
        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    wrapper.__wrapped__ = wrapped
    return wrapper

可以发现,update_wapper的作用就是从 被修饰的函数(wrapped) 中取出一些属性值来,赋值给 修饰器函数(wrapper) 。

例子1:

def wrapper(f):
    def wrapper_function(*args, **kwargs):
        """这个是修饰函数"""
        return f(*args, **kwargs)
    return wrapper_function
    
@wrapper
def wrapped():
    """这个是被修饰的函数"""
    print('wrapped')

print(wrapped.__doc__)  # 输出`这个是修饰函数`
print(wrapped.__name__)  # 输出`wrapper_function`

可以看到,我想要获取wrapped这个被修饰函数的文档字符串,但是却获取成了wrapper_function的文档字符串,wrapped函数的名字也变成了wrapper_function函数的名字。这是因为给wrapped添加上@wrapper修饰器相当于执行了一句wrapped = wrapper(wrapped),执行完这条语句之后,wrapped函数就变成了wrapper_function函数。遇到这种情况该怎么办呢,首先我们可以手动地在wrapper函数中更改wrapper_function的__doc__和__name__属性,但聪明的你肯定也想到了,我们可以直接用update_wrapper函数来实现这个功能。

from functools import update_wrapper

def wrapper(f):
    def wrapper_function(*args, **kwargs):
        """这个是修饰函数"""
        return f(*args, **kwargs)
    update_wrapper(wrapper_function, f)  # <<  添加了这条语句
    return wrapper_function
    
@wrapper
def wrapped():
    """这个是被修饰的函数"""
    print('wrapped')


print(wrapped.__doc__)  # 输出`这个是被修饰的函数`
print(wrapped.__name__)  # 输出`wrapped`

可以发现,__doc__和__name__属性已经能够按我们预想的那样显示了,除此之外,update_wrapper函数也对__module__和__dict__等属性进行了更改和更新。

那么wraps的源码

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                       '__annotations__')
WRAPPER_UPDATES = ('__dict__',)
def wraps(wrapped,
          assigned = WRAPPER_ASSIGNMENTS,
          updated = WRAPPER_UPDATES):
    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)

wraps使用例子:

from functools import wraps

def wrapper(f):
    @wraps(f)
    def wrapper_function(*args, **kwargs):
        """这个是修饰函数"""
        return f(*args, **kwargs)
    return wrapper_function
    
@wrapper
def wrapped():
    """这个是被修饰的函数
    """
    print('wrapped')

print(wrapped.__doc__)  # 输出`这个是被修饰的函数`
print(wrapped.__name__)  # 输出`wrapped`
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值