Python装饰器

在介绍Python的装饰器之前,先得介绍介绍装饰模式(Decorator)。修饰模式主要是动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。

而正式因为装饰模式非常有用,所以Python提供了原生支持。在Python语言中,函数与方法都可以用装饰器来修饰。此外,还有”类装饰器”(class decorator),它也是个单参数的函数,其参数是类,由这种装饰器所返回的新类的名称与原类相同,但功能更多。有时可以通过类装饰器来实现继承。


函数装饰器与方法装饰器

创建一个装饰器的流程:
1. 创建”包装函数”(wrapper function, 我们多以wrapper()来命名)。
2. 在包装函数里调用原函数。调用前可以进行预处理,获取结果之后,还可以执行后加工。包装函数的返回值也很灵活:可以把原函数的调用结果直接返回,也可以先修改再返回,还可以返回其他值。
3. 最后,装饰器把包装函数作为调用结果返回,返回后的函数会以原函数的名义将其取代。

装饰器通常与@开头,放于def/class上方,缩紧相同。

先来看看一个装饰器装饰的函数形式:

@float_arg_and_return
def mean(first, second, *rest):
    numbers = (first, second) + rest
    return sum(number)/len(number)

其实上面的代码可以与下面的代码等价:

def mean(first, second, *rest):
    numbers = (first, second) + rest
    return sum(number)/len(number)
mean = float_args_and_return(mean)

也就是说装饰器就是一种语法糖(多吃糖长身体- -)。

接下来介绍float_args_and_return

def float_args_and_return(function):
    def wrapper(*args, **kwargs):
        args = [float(arg) for arg in args]
        return float(function(*args, **kwargs))
    return wrapper

可以看出function表示传入装饰器的函数,*args, **kwargs代表传入函数的参数,这么写也是函数的所有参数的意思。

现在我们可以看出,mean这个函数接收两个或多个任意类型的参数,并将这些参数均转换为float类型(并未处理异常),然后返回一个这些数的平均值。

但是,上面方法并没有实现完美的包装,因为进过装饰之后的函数的__name__属性就会变为包装函数的__name__,而且即便原函数有docstring也将不存在了,在这里,Python的标准库为我们提供了@functools.wraps装饰器来解决它,我们可以在装饰器里用它来装饰包装函数以解决问题:

def float_args_and_return(function):
    @functools.wraps(function)
    def wrapper(*args, **kwargs):
        args = [float(arg) for arg in args]
        return float(function(*args, **kwargs))
    return wrapper

那现在如果你想在装饰器中传递参数如何才能做到呢?定义一个能制作装饰器的函数!


带参数的装饰器

比如我们希望制作一个装饰器,函数运行时打印log,并打印该函数作者名字进入log,但是可能一个程序是多人合作的结果,此时我们可以将作者名字通过装饰器参数传入,这是我们最终呈现的形式:

@log('Bing')
def dosomething(*args):
    print '这只是个测试而已...'

我们先说明一下编写装饰器工厂(也就是带参数的装饰,等会就知道为何叫做装饰器工厂了)的一般流程:
1. 首先,创建装饰器函数,在该函数内创建包装函数,包装函数的编写方式如前所述。
2. 在包装函数尾部,把调用原函数所得的返回值(也可以修改返回值,或用其他值来替换)返回给上一层。在装饰器函数尾部返回包装函数。
3. 最后,在装饰器工厂函数尾部返回装饰器

那log则如下编写:

def log(author):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print 'run %s, the author is %s' %(func.__name__, author)
            return func(*args, **kwargs)
        return wrapper
    return decorator

创建好包装函数之后,装饰器会将其返回,而log()函数又会在其末尾将装饰器返回。当Python执行到@log('Bing')的时候,它会先调用log()函数,这个函数会返回decorator()函数(这也是它被称为装饰器工厂的原因),现在再看@,Python看到@之后会执行decorator函数,并把log后面的那个函数(do_somethind)传给decorator,此时就是普通装饰器的流程了。


类装饰器

我们经常会创建很多具有可读写属性的类,这些类一般具有大量重复的gettersetter方法。比如,我们需要创建一个Book类,此时需要四个属性:title, ISBN, price, quantity,这时按照一般的方法我们可能需要四个@property修饰器,其代码还都机会一直,此外,还需要四个setter方法,每个方法都要验证用户传入的参数,而验证价格与验证数量都属于验证范围,也是很相似,这样势必造成不必要的重复代码。此时,我么就需要使用到类装饰器了,如:

@ensure("title", is_non_empty_str)
@ensure("isbn", is_valid_isbn)
@ensure("price", is_in_range(1, 10000))
@ensure("quantity", is_in_range(0, 1000000))
class Book:

    def __init__(self, title, isbn, price, quantity):
        self.title = title
        self.isbn = isbn
        self.price = price
        self.quantity = quantity


    @property
    def value(self):
        return self.price * self.quantity


    def __repr__(self):
        return ("Book({0.title!r}, {0.isbn!r}, {0.price!r}, "
                "{0.quantity!r})".format(self))

其中ensure作为类装饰器函数工厂,接收两个参数,一个是属性名,另一个是验证器函数,然后返回一个类装饰器,这个类装饰器会运用在@ensure之后的类上面。

其实整个装饰流程应该等同于:

ensure("title", is_non_empty_str)(
    ensure("isbn", is_valid_isbn)(
        ensure("price", is_in_range(1, 10000))(
            ensure("quantity", is_in_range(0, 1000000))(class Book:...))))

接下来看看ensure函数

def ensure(name, validate, doc=None):
    """This class decorator factory needs a property name and a validate
    function, and will accept a property docstring

    The validate function should return None on success and raise a
    ValueError on failure.
    """
    def decorator(Class):
        privateName = "__" + name
        def getter(self):
            return getattr(self, privateName)
        def setter(self, value):
            validate(name, value)
            setattr(self, privateName, value)
        setattr(Class, name, property(getter, setter, doc=doc))
        return Class
    return decorator

我么可以看出它将给定的函数名全部设置为"__"+name类型,然后内部设置了gettersetter方法,在setter方法中还调用了验证函数。


用类装饰器新增属性

我们这里准备将上述的多个装饰器转换为只用一个类装饰器

@do_ensure
class Book:

    title = Ensure(is_non_empty_str)
    isbn = Ensure(is_valid_isbn)
    price = Ensure(is_in_range(1, 10000))
    quantity = Ensure(is_in_range(0, 1000000))

    def __init__(self, title, isbn, price, quantity):
        self.title = title
        self.isbn = isbn
        self.price = price
        self.quantity = quantity

    @property
    def value(self):
        return self.price * self.quantity

上面是改写后的Book类,我们使用@do_ensure类装饰器与Ensure实例与前例相同的功能。每次构造Ensure对象时,都要传入验证函数,而@do_ensure类装饰器会用带有验证机制的同名属性来替换相应的Ensure实例。

class Ensure(object):

    def __init__(self, validate, doc=None):
        self.validate = validate
        self.doc = doc

def do_ensure(Class):
    def make_property(name, attribute):
        privateName = "__" + name
        def getter(self):
            return getattr(self, privateName)
        def setter(self, value):
            attribute.validate(name, value)
            setattr(self, privateName, value)
        return property(getter, setter, doc=attribute.doc)
    for name, attribute in Class.__dict__.items():
        if isinstance(attribute, Ensure):
            setattr(Class, name, make_property(name, attribute))
    return Class

上面这个类装饰器可分为三部分。在第一部分里,我们定义了名为make_property()的”嵌套函数”。该函数有两个参数,一个是属性名(比如”title”),另一个是Ensure类型的attribute,此函数将返回一个属性,该属性会把其值保存在私有的attribute中(比如title属性的值就保存在名为__titleattribute中)。属性的setter函数还会调用原来Ensure实例的验证器。在第二部分里,我们遍历类中的每一个attribute,并用新的属性来替换原先的Ensure实例。第三部分会把修改后的类返回。

执行完装饰器后,受装饰的类里原有的Ensureattribute都会被同名且带有验证机制的属性所取代。


用类装饰器实现继承

def mediated(Class):
    setattr(Class, 'mediator', None)
    def on_change(self):
        if self.mediator is not None:
            self.mediator.on_change(self)
    setattr(Class, 'on_change', on_change)
    return Class

@mediated
class Button(object):
    def __init__(self, text=''):
        super().__init__()
        self.enabled = True
        self.text = text

    def click(self):
        if self.enabled:
            self.on_change()

其效果等同于:

class Mediated(object):

    def __init__(self):
        self.mediator = None

    def on_change(self):
        if self.mediator is not None:
            self.mediator.on_change(self)

class Button(Mediated):
    pass

最后仰慕般地推荐文章:
Python修饰器的函数式编程
大神讲得详细了很多(虽然文章之间有大神对装饰器和装饰器模式与《Python编程实战》有所出入,但是一个现象各抒己见,学到东西才是真的)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章