Python装饰器的作用是使函数包装和方法包装变得更容易阅读和理解,最常见的就是@staticmethod和@classmethod,下面将从装饰器的表现形式和常用装饰器模式两方面进行描述和总结,若有不正确之处望大家指出。
装饰器表现形式
1. 函数装饰器
编写自定义装饰器有许多方法,但最简单的方法是编写一个函数,返回包装原始函数调用的一个子函数
例1:
#coding=utf-8 def debug(func): def wrapper(*agrs, **kwargs): ‘‘‘包装函数内部文档‘‘‘ print ("[DEBUG]:enter %s()--%s" %(func.__name__, *agrs)) return func(*agrs, **kwargs) return wrapper
@debug def say_hello(parm): ‘‘‘ 提供函数文档字符串‘‘‘ print ("say_hello") if __name__ == "__main__": say_hello("Python") print ("原始函数名:%s" %(say_hello.__name__)) print ("函数文档字符串:%s" %(say_hello.__doc__))
>>> [DEBUG]:enter say_hello()--Python
>>> say_hello
>>> 原始函数名:wrapper
>>> 函数文档字符串:包装函数内部文档
例2:
#coding=utf-8 from functools import wraps def debug(func): @wraps(func) def wrapper(*agrs, **kwargs): ‘‘‘包装函数内部文档‘‘‘ print ("[DEBUG]:enter %s()--%s" %(func.__name__, *agrs)) return func(*agrs, **kwargs) return wrapper @debug def say_hello(parm): ‘‘‘ 提供函数文档字符串‘‘‘ print ("say_hello") if __name__ == "__main__": say_hello("Python") print ("原始函数名:%s" %(say_hello.__name__)) print ("函数文档字符串:%s" %(say_hello.__doc__))
>>> [DEBUG]:enter say_hello()--Python
>>> say_hello
>>> 原始函数名:say_hello
>>> 函数文档字符串: 提供函数文档字符串
注意例1与例2的区别,也是使用装饰器的常用错误,在使用装饰器时不保存函数元数据(文档字符串和原始函数名)
2. 类装饰器
虽然装饰器几乎总是可以用函数来实现,但如果装饰器需要复杂的参数化或者依赖特定状态的话,使用自定义类进行封装可能会更好
#coding=utf-8 from functools import wraps class debug: def __init__(self, func): self.func = func def __call__(self, *argv, **kwargv): ‘‘‘包装函数内部文档‘‘‘ print ("[DEBUG]:enter %s()--%s" %(self.func.__name__, *argv)) self.func(*argv, **kwargv) def say_hello(something): ‘‘‘ 提供函数文档字符串 ‘‘‘ print ("say_hello", something) if __name__ == "__main__": De = debug(say_hello) De("Python") print ("原始函数名:%s" %(say_hello.__name__)) print ("函数文档字符串:%s" %(say_hello.__doc__))
>>> [DEBUG]:enter say_hello()--Python
>>> say_hello Python
>>> 原始函数名:say_hello
>>> 函数文档字符串: 提供函数文档字符串
3. 参数化装饰器
在实际代码中通常需要使用参数化的装饰器,比如次数、类型判断等,下面是一个简单的装饰器示例,给定重复次数,每次被调用时都会重复执行被装饰函数
#coding=utf-8 from functools import wraps #参数化装饰器 def repeat(number=3): def debug(func): @wraps(func) def wrapper(*argv, **kwargv): ‘‘‘包装函数内部文档‘‘‘ for _ in range(number): print ("[DUBEG]:enter %s()--%s" %(func.__name__, *argv)) result = func(*argv, **kwargv) return result return wrapper return debug @repeat(2) def say_hello(*agrv, **kwargv): ‘‘‘提供函数文档字符串‘‘‘ print ("say_hello") if __name__ == "__main__": say_hello("Python") print ("原始函数名:%s" %(say_hello.__name__)) print ("函数文档字符串:%s" %(say_hello.__doc__))
>>> [DUBEG]:enter say_hello()--Python
>>> say_hello
>>> [DUBEG]:enter say_hello()--Python
>>> say_hello
>>> 原始函数名:say_hello
>>> 函数文档字符串:提供函数文档字符串
常用装饰器模式
1. 参数检查
将函数注册到全局字典中,并将其参数和返回值保存在一个类型列表中,并对参数类型进行检测
#coding=utf-8 ‘‘‘将函数注册到全局字典中,并将其参数和返回值保存在一个类型列表中‘‘‘ funname = {} def parmcheck(in_= (type(None),), out_ =(type(None), )): def fun1(func): func_name = func.__name__ print ("funname:%s" %(func_name)) funname[func.__name__] = (in_, out_) def checkType(elements, types): ‘‘‘用来检查参数类型的子函数‘‘‘ if len(elements) != len(types): raise TypeError("Parm count is wrong!") li = zip(elements, types) typed = enumerate(li) for index,couple in typed: argv, intype = couple if isinstance(argv, intype): print ("parm(%s) and type(%s)are all right" %(argv, intype)) continue raise TypeError("argv %d should be %s" %(argv, intype)) def decoratorfun(*argv): #types = [type(i) for i in range(len(argv))] #checkType(argv, types) checkType(argv, in_) res = func(*argv) #检查输出内容 if type(res) not in (tuple, list): checkable_res = (res, ) else: checkable_res = res checkType(checkable_res, out_) return decoratorfun return fun1 @parmcheck((int,int)) def meth1(a,b): print ("received:%d,%d" %(a, b)) if __name__=="__main__": meth1(1,2) print (funname)
>>> funname:meth1
>>> parm(1) and type(<class ‘int‘>)are all right
>>> parm(2) and type(<class ‘int‘>)are all right
>>> received:1,2
>>> parm(None) and type(<class ‘NoneType‘>)are all right
>>> {‘meth1‘: ((<class ‘int‘>, <class ‘int‘>), (<class ‘NoneType‘>,))}
注意zip、enumerate、解包、isinstance方法的使用
2. 缓存
缓存装饰器与参数检查十分相似,它的重点是关注那些内部状态不会影响输出的函数,每组参数都可以连接到唯一的结果
#coding = utf-8 import time import pickle import hashlib #全局字典 cache = {} def is_obsolete(entry, duration): print (time.time() - entry["time"]) return time.time() - entry["time"] > duration def compute_key(func, *argv, **kwargv): key = pickle.dumps((func.__name__, argv, kwargv)) return hashlib.sha1(key).hexdigest() def memoize(duration=10): def _memoize(func): def __memoize(*argv, **kwargv): key = compute_key(func,*argv, **kwargv) if ((key in cache) and not is_obsolete(cache[key], duration)): print ("we got a winner") return cache[key][‘value‘] result = func(*argv, **kwargv) cache[key]={"value":result,"time":time.time()} return result return __memoize return _memoize @memoize(3) def fun(a,b): print (a+b) return a+b if __name__=="__main__": fun(2,3) fun(2,2) fun(2,3) print (cache)
>>> 5
>>> 4
>>> 0.0
>>> we got a winner
>>> {‘a99634a4e619a2ad129df1b51002a8c0cb9cca2b‘: {‘value‘: 5, ‘time‘: 1518243058.456
>>> 425}, ‘99683ddc4e22fd3f37e473de5d61699a5c27c2c6‘: {‘value‘: 4, ‘time‘: 151824305
>>> 8.456425}}
3. 代理
代理装饰器使用全局机制来标记和注册函数。比如一个根据当前用户来保护代码访问的安全层可以使用集中式检查器和相关的可调用对象要求的权限来访问,这一模型常用于Python Web框架中,用于定义法布类的安全性
4. 上下文提供者
上下文装饰器确保函数可以允许在正确的上下文中,比如临界资源的使用
#coding=utf-8 from threading import RLock lock = RLock() def synchronized(func): def _synchronized(*argv, **kdargv): lock.require() try: return func(*argv, **kdargv) except: print ("fun error!!!") finally: lock.release() return _synchronized
上下文装饰器通常会被上下文管理器with语句代替