6

decorator

装饰器

简单来说,装饰器算得上是一种语法糖,简化写法,与C中的数组写法有些相似,在装饰器中,装饰器的第一个参数一般是一个被装饰的函数,装饰器函数一般返回一个包装函数。

#python3.5
import time
from functools import wraps
def timethis(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end-start)
        return result
    return wrapper

@timethis
def countdown(n:int):
    while n > 0:
        n -= 1

可以接收可选参数的装饰器

有些的装饰器的参数是可选的,即,可以给它传入参数,也可以不给它参数,这样看似简单的实现其实属于两种不同的调用。 为了保证编程的一致性,即,使参数成为可选的。当然参数为关键参数,而不是位置参数。下面的例子也就是一个可接受可选参数的装饰器,不过对python的版本有要求,在python3中才可以。

# python3.5
from functools import wraps, partial
def logged(func=None, *, level=loggin.DEBUG, name=None, message=None):
    if func is None:
        return partial(logged, level=level, name=name, message=message)
    logname = name if name else func.__module__
    log = logging.getLogger(logname)
    logmsg = message if message else func.__name__

    @wraps(func)
    def wrapper(*args, **kwargs):
        log.log(level, logmsg)
        return func(*args, **kwargs)
    return wrapper

@logged
def add(x, y):
    return x + y

@logged(level=logging.CRITICAL, name='example')
def spam():
    print('Spam!')

在上面的例子中,add的调用的方式为add = logged(add); 而spam的调用方式为spam = logged(level=logging.CRITICAL, name='example')(spam)。装饰器的参数此时即为可选的。

常见的内建装饰器

@classmethod

class Date(object):
    day = 0
    year = 0
    month = 0

    def __init__(self, day=0, year=0, month=0):
        self.day = day
        self.year = year
        self.month = month

    @classmethod
    def from_string(cls, date_as_string):
        year, month, day = map(int, date_as_string.split('-'))
        return cls(day, year, month)

@staticmethod

class Date(object):
    day = 0
    year = 0
    month = 0

    def __init__(self, day=0, year=0, month=0):
        self.day = day
        self.year = year
        self.month = month

    @staticmethod
    def is_date_valid(date_as_string):
        year, month, day = map(int, date_as_string.split('-'))
        return day <= 31 and month <= 12 and year <= 3999

@wrap

from functools import wrap
import time
def outer(func):
    @wrap(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        func(*args, **kwargs)
        end = time.time()
        print(end - start)
    return wrapper

@outer
def add(x, y):
    return x + y

>>>add(1, 2)
>>>add.__name__ # add
#去掉wraps
>>>add.__name__#wrapper

装饰器的形式

function

通用的形式就是使用函数的形式来制作装饰器

#python2.7/3,5
from functools import wraps
import sys.stdout
class A(object):
    def decorator1(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            stdout.write('Deractor 1')
            return func(*args, **kwargs)
        return wrapper

    @classmethod
    def decorator2(cls, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            stdout.write('Deractor 2')
            return func(*args, **kwargs)
        return wrapper

class

类形式的装饰器需要在类中实现__call__, __get__方法

#python3.5
import types
from functools import wraps

class Profiled(object):
    def __init__(self, func):
        wraps(func)(self)
        self.ncalls = 0
    def __call__(self, *args, **kwargs):
        self.ncalls += 1
        return self.__wrapped__(*raps, **kwargs)
    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return types.MethodType(self, instance)

装饰器的常见用途

参数检查

缓存信息

代理模式

import sys.stdout

class User(object):
    def __init__(self):
        self.roles = roles

class Unauthorized(Exception):
    pass

def protect(role):
    def _protect(function):
        def __protect(*args, **kwargs):
            user = globals().get('user')
            if user is None or role not in user.roles:
                raise Unauthorized('i won\'t tell you')
            return function(*args, **kwargs)
        return __protect
    return _protect

>>>tarek = User(('admin', 'user'))
>>>bill = User(('user',))
>>>class MySecrets(object):
            @protect('admin')
            def look(self):
                stdout.write('hello, the role is ok!')
>>>secret = MySecrets()
>>>user = tarek
>>>secret.look() #hello, the role is ok!
>>>user = bill
>>>secret.look()#error 

上下文提供

提供上下文作用域的装饰器可以保证函数运行在恰当的作用域内,它可以保证函数设置和取消在特定的作用域内。例如,当一个数据在多个线程之间进行共享的时候,加一个锁可以用来保证数据不会被其他线程接触。

#python3.5
from threading import RLOCK
lock = RLOCK()

def synchronized(function):
    def _synchronized(*args, **kwargs):
        lock.acquire()
        try:
            return function(*args, **kwargs)
        finally:
            lock.release()
    return _synchronized

@synchronized
def thread_safe():
    pass
descriptor