python中使用装饰器

python中使用装饰器

装饰器本质上是一个函数,这个函数接收其他函数作为参数,并将其以一个新的修改后的函数进行替换。最简单的装饰器函数可能就是本体函数(identity function),它除了返回原函数什么都不做。

def identity(f):
    return f

然后就可以像下面这样使用这个装饰器了:

@identity
def foo():
    return 'bar'

它和下面的过程类似:

def foo():
    return 'bar'

foo = identity(foo)

这个装饰器没什么用,但确实可以正常运行。只不过它什么也不做。

_functions = {}
def register(f):
    global _functions
    _functions[f.__name__] = f
    return f

@register
def foo():
    return 'bar'

在上面的例子中,函数被注册并存储在一个字典里,以便后续可以根据函数名字提取函数。


装饰器的主要应用场景:针对多个函数提供在其之前,之后或者周围进行调用的通用代码。

考虑这样一组函数,它们在被调用时需要对作为参数接收的用户名进行检查:

class Store(object):
    def get_food(self, username, food):
        if username != 'admin':
            raise Exception("this user is not allowed to get food")
        return self.storage.get(food)

    def put_food(self, username, food):
        if username != 'admin':
            raise Exception("this user is not allowed to get food")
        return self.storage.put(food)

显然,第一步就是要分离出检查部分的代码:

def check_is_admin(username):
    if username != 'admin':
        raise Exception("this user is not allowed to get food")

class Store(object):
    def get_food(self, username, food):
        check_is_admin(username)
        return self.storage.get(food)

    def put_food(self, username, food):
        check_is_admin(username)
        return self.storage.put(food)

现在代码看上去稍微整洁了一点。但有了装饰器能做的更好:

def check_is_admin(f):
    def wrapper(*args, **kwargs):
        if username != 'admin':
            raise Exception("this user is not allowed to get food")
        return f(*args, **kwargs)
    return wrapper

class Store(object):
    @check_is_admin
    def get_food(self, username, food):
        return self.storage.get(food)

    @check_is_admin
    def put_food(self, username, food):
        return self.storage.put(food)

类似这样使用装饰器,会让常用函数的管理更容易。如果有过正式的python经验的话,这有点儿老生常谈,但你可能没有意识到这种原生实现装饰器的方式有一些主要的缺陷。


正如前面提到的,装饰器会用一个动态创建的新函数来替换原有的。然而,新函数缺少很多原函数的属性,比如docstring和名字。

def foobar(username="someone"):
    """do crazy stuff"""
    pass

foobar = functools.update_wrapper(is_admin, foobar)
print foobar.__name__   #输出结果:'foobar'
print foobar.__doc__    #输出结果:'do crazy stuff'

幸好,python内置的functools模块通过其update_wrapper函数解决了这个问题,它会复制这些属性给这个包装器本身。就像上面的例子中演示的一样。

手工调用update_wrapper创建装饰器很不方便,所以functools提供了名为wraps的装饰器,就像下面:

def check_is_admin(f):
    @functools.wraps(f)     #在此处使用functools.wraps
    def wrapper(*args, **kwargs):
        if username != 'admin':
            raise Exception("this user is not allowed to get food")
        return f(*args, **kwargs)
    return wrapper

目前为止,在我们的示例中总是假设被装饰的函数会有一个名为username的关键字参数传入,但实际情况并非总是如此。考虑到这一点,最好提供一个更加智能的装饰器,它能查看被装饰函数的参数,并从中提取需要的参数。

def check_is_admin(f):
    @functools.wraps(f)
    def wrapper(*args, **kwargs):
        # inspect.getcallargs将返回一个以参数名字和值作为键值对的字典。此时,装饰器函数不用关心username是基于位置的参数还是关键字参数
        func_args = inspect.getcallargs(f, *args, **kwargs)
        if func_args.get('username') != 'admin':
            raise Exception("this user is not allowed to get food")
        return f(*args, **kwargs)
    return wrapper
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值