Python装饰器

  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
>>> 函数文档字符串:提供函数文档字符串

4. 装饰器装饰类

  和装饰一个函数类似,也可以写一个函数来装饰类,用来向类中添加功能,基本原则一致,装饰器是一个函数或是一个可调用对象,它接受一个类作为参数,返回一个类作为返回值

#coding = utf-8

def decoratortest(cls):
    print ("{0.__class__.__qualname__}".format(cls))
    return cls

@decoratortest
class testclass:
    def __init__(self, value):
        self.value = value

    def __repr__(self):
        return "{0}:88".format(self)    

if __name__ == "__main__":
 
    t = testclass(88)

常用装饰器模式

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语句代替
posted @ 2018-02-10 14:35  Fate0729  阅读(1249)  评论(0编辑  收藏  举报