装饰器是在Python2.4中新加入的,它使得函数和方法封装(接收一个函数并返回增强版本的一个函数)更容易阅读和理解。原始的使用场景是可以将方法在定义的首部将其定义为类方法或静态方法。在添加装饰器之前,相应的语法如下:
class WhatFor(object):
def it(cls):
print 'work with %s' % cls
it = classmethod(it)
def uncommon():
print 'I could be a global function'
uncommon = staticmethod(uncommon)
如果方法很大,或者在该方法中有多个跳转时,这种语法将变得难以阅读。使用了装饰器之后,其语法更加浅显易懂,如下:
>>>class WhatFor(object):
@classmethod
def it(cls):
print 'work with %s' % cls
@staticmethod
def uncommon():
print 'I could be a global function'
>>>this_is = WhatFor()
>>>print this_is.it()
work with <class '__main__.WhatFor'>
>>>print this_is.uncommon()
I could be a global function
1. 如何编写装饰器
编写自定义装饰有许多方法,但最简单和容易理解的方式是编写一个函数,返回封装原始函式调用的一个子函数。通用模式如下:
def mydecorator(function):
def _mydecorator(*args, **kw):
red = function(*args,**kw)
return res
return _mydecorator
为子函数应用一个诸如_mydecorator之类的明确的名称,而不是wrapper这样的通用名称是一个好习惯,因而明确的名称更方便在错误发生时回溯----可以知道正在处理制定的装饰器。当装饰器需要参数时,必须使用第二级封装。
def mydecorator(argv1,artv2):
def _mydecorator(function):
def __mydecorator(*args,**kw):
#在调用实际函数之前做些填充工作
res = function(*args, **kw)
return res
#返回子函数
return __mydecorator
return _mydecorator
因为装饰器在模块第一次被读取时由解释程序装入,所以它们的使用必须限制于总体上可以应用的封装器。如果装饰器与方法的类或所增强的函数签名绑定,它应该被重构为常规可调用对象,从而避免复杂性。在任何情况下,当装饰器处理API时一个好方法是将它们聚集在一个易于维护的模块中。
装饰器应该关注所封装的函数或方法接收和返回的参数。如果需要,应该尽可能限制其内省工作。常见的装饰器模式包括:
- 参数检查
- 缓存
- 代理
- 上下文提供者
2. 参数检查
检查函数接收或返回的参数,在特定上下文执行时有可能有用。例如,如果一个函数通过XML-RPC调用,Python将不能和静态类型语言中一样直接提供它的完整签名。当XML-RPC客户要求函数签名时,就需要这个功能提供内省能力。
装饰器能提供这种签名类型,并确保输入输出与此相关,如下所示:
#-*- coding:utf-8 -*-
from itertools import izip
rpc_info = {}
def xmlrpc(in_ = (), out = (type(None),)):
def _xmlrpc(function):
func_name = function.func_name #注册函数名
rpc_info[func_name] = (in_, out)
def _check_types(elements,types):
if len(elements) != len(types):
raise TypeError('argument count is wrong')
typed = enumerate(izip(elements, types))
for index, couple in typed:
arg, of_the_right_type = couple
if isinstance(arg, of_the_right_type):
continue
raise TypeError('arg #%d should be %s' % (index, of_the_right_type))
# 封装函数
def __xmlrpc(*args): #没有允许的关键字
# 检查输入内容
checkalbe_args = args[1:] # removing self
_check_types(checkalbe_args, in_)
#执行该函数
res = function(*args)
# 检查输出内容
if not type(res) in (tuple, list):
checkalbe_res = (res,)
else:
checkalbe_res = res
_check_types(checkalbe_res, out)
# 函数及其类型检查成功
return res
return __xmlrpc
return _xmlrpc
class RPCView(object):
@xmlrpc((int, int))
def meth1(self, int1, int2):
print "rceived %d and %d" % (int1, int2)
@xmlrpc((str,),(int,)) # string --> int
def meth2(slf, phras):
print 'rceived %s' % phras
return 12
def main():
print rpc_info
my = RPCView()
my.meth1(1,2)
my.meth2(2)
if __nam__ == "__main__":
main()
装饰器将该函数注册到全局字典中,并为其参数和返回保存一个类型列表。结果为:
{'meth2': ((<type 'str'>,), (<type 'int'>,)), 'meth1': ((<type 'int'>, <type 'int'>), (<type 'NoneType'>,))}
rceived 1 and 2
Traceback (most recent call last):
File "decorator-1.py", line 49, in <module>
main()
File "decorator-1.py", line 46, in main
my.meth2(2)
File "decorator-1.py", line 20, in __xmlrpc
_check_types(checkalbe_args, in_)
File "decorator-1.py", line 17, in _check_types
raise TypeError('arg #%d should be %s' % (index, of_the_right_type))
TypeError: arg #0 should be <type 'str'>
参数检查装饰器有许多其他应用场景,如类型强制,可以根据给定全局配置值来定义多种级别的类型检查:
- 什么都不检查
- 检查器仅弹出警告
- 检查器将抛出TypeError异常
3.缓存
缓存装饰器与参数检查很相似,不过它关注于内部状态而不影响输出的函数。每组参数可以连接到一个唯一的结果上。这种编程风格是函数型编程的特性,当输入值有限时可以使用。
因为缓存装饰器可以将输出与计算它所需的参数放在一起,并且直接在后续的调用中返回它。这种行为被称为
Memoizing(常译作自动缓存),很容易被实现为一个装饰器。
import time
import hashlib
import pickle
from itertools import chain
cache = {}
def is_obsolete(entry, duration):
return time.time() - entry['time'] > duration
def compute_key(function, args, kw):
key = pickle.dumps((function.func_name, args, kw))
key_val = hashlib.sha1(key).hexdigest()
return key_val
def memoize(duration=10):
def _memoize(function):
def __memoize(*args, **kw):
key = compute_key(function, args, kw)
if(key in cache and
not is_obsolete(cache[key], duration)):
print 'we got a winner'
return cache[key]['value']
result = function(*args, **kw)
cache[key] = {'value':result, 'time': time.time()}
return result
return __memoize
return _memoize
@memoize(15)
def stuff(a,b):
return a+b
print stuff(1,3)
time.sleep(2)
print stuff(1,3)
print cache
结果如下:
4
we got a winner
4
{'eb2f00ec12f316e87e306390314388a2e4af78c0': {'value': 4, 'time': 1422183561.877448}}
在该例子中,将函数和参数进行了序列化,并作为key保存在一个字典(cache)中,下次再次运行该函数的时候,首先从cache中检查是否存在,如果存在且还没有过期,则直接从cache中拿出结果,否则,将结果和时间保存在缓存中。应用缓存的函数可以显著提高程序的总体性能,但是必须小心使用。缓存值可能绑定到函数本省上,以管理范围和生命周期,代替集中化的字典。但是在任何情况下,更有效的装饰器应该使用基于高级缓算法的专门化缓存库,并使用备份分布式缓存功能的Web应用程序上。Memcached是Python中使用的算法之一。
4. 代理
代理装饰器使用一个全局机制来标记和注册函数。例如,一个根据当前用户保护代码访问的安全层使用一个集中检查器和相关的可调用对象要求的权限来实现,如下:class User(object):
def __init__(self, roles):
self.roles = roles
class Unauthorized(Exception):
pass
def protect(role):
def _protech(function):
def __protech(*args, **kw):
user = globals().get('user')
if user is None or role not in user.roles:
raise Unauthorized("I won't tell you")
return function(*args, **kw)
return __protech
return _protech
tarek = User(('admin', 'user'))
bill = User(('user',))
class MySecrets(object):
@protect('admin')
def w(self):
print 'use tons of butter!'
th = MySecrets()
user = tarek
th.w()
user = bill
th.w()
这种模式常常应用于Python Web框架,用来定义针对可法布类的安全性。例如,Django提供创使其来保障函数访问的安全。一下是一个实例,当前用户保存在一个全局变量中。装饰器在方法被访问时检查其角色,结果如下:
use tons of butter!
Traceback (most recent call last):
File "User.py", line 32, in <module>
th.w()
File "User.py", line 13, in __protech
raise Unauthorized("I won't tell you")
__main__.Unauthorized: I won't tell you
5. 上下文提供者
上下文装饰器用来确保函数可以运行在正确的上下文中,或者在函数前后执行一些代码。换句话说,它用来设置或者复位替丁的执行环境。例如,当一个数据必须与其他线程共享时,就需要使用一个锁来确保他的多重访问时得到保护。这个锁就可以在一个装饰器中编写,实例如下:
from threading import RLock
lock = RLock()
def locker(function):
def _synchronize(*args, **kw):
lock.acquire()
try:
return function(*args, **kw)
finally:
lock.release()
return _synchronize
@locker
def thread_safe():
pass
上下文装饰器可以使用Python2.5中新添加的with语句来代替。创造这条语句的目的是使用try ... finally模式更加流畅,在某些情况下,它覆盖了上下文装饰器的使用场景。