装饰器,作为Python中的一个非常重要的功能,在web系统,日志打印等领域中有着很广泛的应用,比如Flask和Django框架的代理机制就是使用了装饰器。这里,我将总结《Expert Python Programming》第2版的装饰器部分我认为重点的内容,并配合例子进行说明。
1、Python的装饰器(Decorator)是什么?
Python装饰器,简单来讲,就是使函数包装和类的方法包装(一个函数,接受函数并返回其增强函数 (对原函数增加一些日志信息或更高级的处理的方式))变得容易阅读和理解。其最初的使用场景是在方法定义的开头,将一个类的方法定义为静态方法/类方法——假设代码最后的staticmethod
和classmethod
是预先定义好的:
class WithOutDecorators:def static_method_A():print("This is a static method A")def class_method_B():print("This is a class method B")static_method_A = staticmethod(static_method_A)class_method_B = classmethod(class_method_B)
如果用装饰器(decorator)写的话,可以想到,对于有很多静态方法和类方法的类来讲,采取decorator机制会使得代码可读性更强、容易理解的优点。
class WithDecorators:@staticmethoddef static_method_A():print("This is a static method A")@classmethoddef class_method_B():print("This is a class method B")
2、装饰器(Decorator)一般用法和可能实现
装饰器(Decorator)通常是一个命名的对象——(注意,不允许使用lambda表达式!),在被装饰函数调用时,接受单一参数,并返回另一个可调用的对象(callable object)。所以,任何可调用对象(类内部实现了__call__
方法的对象)都可以用作装饰器。他们返回的对象也不是简单的函数,也可能是实现了自己的__call__
方法的复杂类的实例(instance)。
事实上,任何函数都可以用作decorator,因为python没有规定装饰器的返回类型。因此可以基于此做一些有趣的实验——比如将str()作为装饰器,虽然会报错,但是也是蛮有意思。
① 作为函数
最简单也是最常用的用法,就是编写一个函数作为Decorator,通用模式为:
def mydecorator(func):def wrapped(*args, **kwargs):print("调用函数之前处理...")result = func(*args, **kwargs)print('函数调用完成之后处理...')return resultreturn wrapped@mydecorator
def calc(n):assert type(n) == int, "输入务必是integer"number = n*2print(number)return numbercalc(10)
② 作为类
虽然decorator总是几乎可以用函数实现,但是某些情况下,使用用户self-define的类可能会更好————比如需要复杂参数化或者依赖于特定状态的decorator,那么这种说法往往是对的。
通用模式如下:
class DecoratorAsClass(object):def __init__(self, func):self.func = funcdef __call__(self, *args, **kwargs):print("类装饰器:调用函数之前处理...")result = self.func(*args, **kwargs)print("类装饰器:调用函数之后处理...")return result@DecoratorAsClass
def calc2(n):assert type(n) == int, "输入务必是integer"number = n*10print(number)return numbercalc2(50)
③ 参数化装饰器
实际代码中,通常需要使用参数化的装饰器。如果用函数作为装饰器的话,那么解决方式很简单————需要在wrap一层(也就是从之前2层变成了3层):
def repeat(number):"""多次重复执行装饰函数重复次数为number的数目。"""def actual_decorator(func):def wrapped(*args, **kwargs):result = Noneprint("参数化装饰器:调用函数之前处理...")for i in range(number):result = func(*args, **kwargs)print('参数化装饰器:函数调用完成之后处理...')return resultreturn wrappedreturn actual_decorator@repeat(3)
def calc3(n):assert type(n) == int, "输入务必是integer"print(n)return ncalc3(100)
- ④ 保存内省的装饰器(保留被封装的原函数名称和文档)
这块没细看,有兴趣的同学可以看书学习。
3、装饰器(Decorator)使用和有用例子
由于装饰器在module被首次读取的时候由解释器(intepreter)来加载,所以其使用受限于通用的包装器(wrapper),如果装饰器与方法的类或所增强的函数签名绑定,应该将其重构为常规的可调用对象,以避免复杂性。
常规的装饰器模式如下:
(1)参数检查
其目的是:检查函数接受或者返回的参数,在特定的上下文中执行有用。书中的例子是:“当一个函数通过XML-RPC来调用,那么Python无法像静态语言C/C++一样,直接提供其完整的签名。当XML-RPC客户端请求函数签名时,就需要用这功能来提供内省能力。”
也就是说,在一些web应用中,采用Decorator机制的参数检查会发挥作用,这块我暂时没看。
(2)缓存
缓存和参数检查十分相似,不过,它重点关注的是不受状态影响的函数,即唯一确定的参数一定会产生唯一确定的结果,这就是函数式编程(functional programming)的风格。
因此,缓存decorator可以将输出与计算其所需要的参数放在一起,并在后续的调用中直接返回它。这种行为被称为memoizing。
下面就是一个装饰器的实现,其中
is_obsolete
函数是判断某组(函数,参数)的计算结果是否超过cache的固定时间,如果超过就返回yes。
compute_key
的作用是计算某组(函数,参数)的签名。
一个明显的问题就是,cache到时了,字典中对应的结果没被擦除,这块可以根据自己的需要个性定制。有想更深入学习缓存装饰器的,建议看一下functools里面的lru_cache,它也是以装饰器的形式使用。网上也有比较详实的说明介绍。
(3)代理(Web)
代理装饰器使用全局机制来标记和注册函数。举个例子,一个根据当前用户来保护代码访问的安全层可以使用集中式检查器和相关的可调用对象要求的权限来实现。
这种模型通常用于Python的Web框架中,用于定义可发布类的安全性。例如,Django提供decorator来保护函数访问的安全。
下面的简单例子是,当前用户被保存在全局变量(用到了python的全局机制,globals()
)。在方法被访问的时候decorator会检查其角色,看是否符合。
对复杂情况,可以对每个角色的分工设置权限,比如某个用户只能看到网页A,B,而root用户能看到网页A,B,C,D这种设置。
(4) 上下文提供者(通常被上下文管理器(with …)所替代)
上下文装饰器确保函数可以运行在正确的上下文中,或者在函数前后运行一些代码。换句话说,它设定并复位一个特定的执行环境。
举个例子,当一个数据需要在多个thread之间进行共享的时候,需要锁来保护它被多次访问(临界区)。这个锁可以在装饰器中编写,代码如下: