python设计模式之装饰器详解(三)

python的装饰器使用是python语言一个非常重要的部分,装饰器是程序设计模式中装饰模式的具体化,python提供了特殊的语法糖可以非常方便的实现装饰模式。

系列文章

装饰模式简介

  • 什么叫装饰?简单来说就是在已有的对象上添加格外的属性、方法或一段代码实现一个新的功能但是又不改变原来的对象或上下文的结构;这样做的意义在于为了是程序的设计符合开放-封闭原则,即对扩展开放,但是对修改封闭;也就是说一个类或方法被定义完成后就不要再去修改它了,你可以通过继承、装饰、代理等一些模式去扩展它的功能;最终的目的是为了降耦合和保证系统的稳定性。

python装饰模式的简单实现

class Car(object):
    """
    一个汽车类
    """
    def __init__(self):
        self.logo = "奔驰" # 车标
        self.oil = 2 # 油耗
        self.ornamental = None # 装饰品

    # 安装空调
    def air_conditioner(self):
        print("空调真舒服!")

    def decorator(self, component):
        """用来额外添加装饰方法的"""
        self.ornamental = component

# 由于汽车的装饰品会发生变化
class Cushion(object):
    """
    坐垫
    """
    def __init__(self):
        self.name = "席梦思"

class Flower(object):
    """
    装饰花
    """
    def __init__(self, name):
        self.name = name

if __name__ == '__main__':
    car = Car()
    cushion = Cushion()
    flower = Flower("玫瑰")
    # 汽车添加一个坐垫
    car.decorator(cushion)
    print(car.ornamental)
    # 添加一个花
    car.decorator(flower)
    print(car.ornamental)

上例中坐垫和花是可以为汽车动态添加的额外的属性,这样的话可以精简Car类的结构,同时可以扩展Car类的功能。

python的装饰器

  • python装饰器主要针对的是为一段已完成的方法增加额外的需要执行的代码,为了保持原来的方法不变,装饰器装饰后应该返回一个新的方法;实现这种功能需要一个载体,这个载体可以是函数也可以是类,同时python提供了语法糖@来完成装饰功能。

python装饰器实现原理介绍

@decorator
def get_name():
    pass

如上所示,当代码初始化加载上下文的时候,先定义get_name函数,decorator一定要在定义get_name函数之前加载到上下文中,解释器遇到@这种语法糖,会将@下的get_name函数作为参数代入decorator中执行decorator并返回新的方法,重新将返回的方法赋值给get_name。

那decorator究竟是什么结构呢?我们可以看装饰器的要求,需要返回一个新的方法,因此可以是闭包结构,也可以是类结构。

使用闭包结构的装饰器

def decorator(func):
    def new_func(*args,**kwargs):
        "do something"
        res = func(*args,**kwargs)
        "do something"
        return res

    return new_func

@decorator
def get_name():
    pass

什么是闭包?在函数中定义一个新的函数,这个函数用到了外边函数的变量,将这个函数以及用到的一些变量称之为闭包。闭包函数在初始化上下文的时候是不会被定义的,只有在执行的时候才会被定义。

上例所示,get_name函数作为参数传递到decorator函数中执行decorator函数返回new_func,new_func函数的参数就是get_name函数的参数,new_func赋值给get_name。此时get_name方法中添加了额外的代码。

  • 装饰器带参数

如果需要在原来的装饰器上还需要添加额外的参数,那就必须使用双层闭包结构了。

def new_decorator(pas=""):
    def decorator(func):
        def new_func(*args,**kwargs):
            "do something"
            print(pas)
            res = func(*args,**kwargs)
            "do something"
            return res

        return new_func
    return decorator


@new_decorator('new prams')
def get_name():
    pass

如上所示,pas参数会被传递到闭包函数中去,此时加载上下文时,new_decorator由于自带了()形式,会被直接执行,返回内层decorator函数,然后按照正常的方式装饰过程执行,pas参数由于被内层函数引用住,会长久地驻留在内存中而不会被释放。

  • 多层装饰

如果在已被装饰的函数上再添加额外的代码功能,就需要再添加装饰器了,此时的重点就在于装饰器函数的执行顺序了。

def new_decorator(pas=""):
    def decorator(func):
        def new_func(*args,**kwargs):
            "do something"
            print(pas)
            res = func(*args,**kwargs)
            "do something"
            return res

        return new_func
    return decorator

def tow_decorator(func):
    def new_func(*args, **kwargs):
        res = func(*args, **kwargs)
        "do other something"
        return res

    return new_func

@tow_decorator
@new_decorator(pas='hhhhh')
def get_name():

装饰器函数的执行顺序不同于我们的习惯,其按就近原则,即如上new_decorator会先执行装饰,将返回的函数赋值给get_name,然后依次往上;因此装饰代码的整体结构为tow_decorator添加的额外代码包裹new_decorator的代码,这在需要考虑代码的执行顺序是很重要。

使用类结构的装饰器

class Decorator(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        """
        添加额外的代码
        :param args:
        :param kwargs:
        :return:
        """
        print('do something')
        return self.func(*args, **kwargs)

@Decorator
def get_name():
    pass

print(isinstance(get_name, Decorator))

# 结果
True

如上,由于在python中,一个对象只要具备了__call__方法,就可以使用xxx()的形式进行调用执行call方法中的代码,利用的这个原理我们就可以实现装饰器的结构;

初始化上下文时,执行Decorator,get_name作为参数对Decorator的实例进行初始化,返回一个Decorator的实例赋值给get_name,此时get_name是一个类的实例对象,当调用get_name时会执行Decorator实例对象的call方法。但这种方式相对于闭包结构来说就有点复杂了,一般不用。

  • 装饰器带参数结构

如果使用类结构需要添加额外的参数怎么办呢?和闭包结构一样,再添加一层。

def three_decorator(pas=''):
    class Decorator(object):
        def __init__(self, func):
            self.func = func
            self.pas = pas

        def __call__(self, *args, **kwargs):
            """
            添加额外的代码
            :param args:
            :param kwargs:
            :return:
            """
            print('do something')
            return self.func(*args, **kwargs)

    return Decorator

@three_decorator('hhhhh')
def get_name():
    pass

使用装饰器的副作用

装饰器的用处不用多说,函数校验、进入日志、函数执行前后处理等多场景都需要用到,它也有一点副作用。

def tow_decorator(func):
    def new_func(*args, **kwargs):
        """new_func function"""
        res = func(*args, **kwargs)
        print("do other something")
        return res

    return new_func

@tow_decorator
def get_name():
    """get_name function"""
    print('jjjjjj')
if __name__ == '__main__':
    import inspect
    print(inspect.signature(get_name))
    x = inspect.getsource(get_name)
    print(x)
    print(get_name.__doc__)

# 结果
(*args, **kwargs) # 函数签名
    def new_func(*args, **kwargs): # 函数源码
        """new_func function"""
        res = func(*args, **kwargs)
        print("do other something")
        return res
‘new_func function’ # 函数文档

可以看到,由于get_name被装饰后指向的是new_func函数,所以获取的信息不再是get_name函数的说明了,对于调试是不方便的。我们可以使用functools模块的wraps函数基本消除这个副作用。

def tow_decorator(func):
    @functools.wraps(func)
    def new_func(*args, **kwargs):
        """new_func function"""
        res = func(*args, **kwargs)
        print("do other something")
        return res

    return new_func

@tow_decorator
def get_name():
    """get_name function"""
    print('jjjjjj')
if __name__ == '__main__':
    print(get_name.__doc__)

# 结果
()
@tow_decorator
def get_name():
    """get_name function"""
    print('jjjjjj')
get_name function

创建装饰器的一般方式

  • 闭包结构方式

根据我们上面的分析,标准的装饰器结构:

import functools
def decorator(func):
    @functools.wraps(func)
    def new_func(*args,**kwargs):
        "do something"
        res = func(*args,**kwargs)
        print("do something")
        return res

    return new_func
  • 使用decorator.py模块的decorator和decorate

闭包结构的装饰函数不太直观,我们可以使用第三方包类改进这样的情况,让装饰器函数可读性更好。

from decorator import decorator, decorate, contextmanager
def wrapper(func, *args, **kwargs):
    """装饰函数的内容"""
    res = func(*args, **kwargs)
    print("do other something")
    return res

def one_decorator(func):
    """进行装饰"""
    return decorate(func, wrapper)

@one_decorator
def get_name(pas='fff'):
    """get_name function"""
    print('jjjjjj')

if __name__ == '__main__':
    import inspect
    print(inspect.signature(get_name))
    x = inspect.getsource(get_name)
    print(x)
    print(get_name.__doc__)
    get_name()

将新函数单独写出来,使得原来的闭包结构变成了两个函数,代码可读性变好了,可以发现decorate帮我们处理了装饰器的副作用,但是一个装饰器需要定义两个函数有点麻烦,要更优雅使用decorator。

from decorator import decorator, decorate, contextmanager

@decorator
def five_decorator(func, *args, **kwargs):
    res = func(*args, **kwargs)
    print("do other something")
    return res

@five_decorator
def get_name(pas='fff'):
    """get_name function"""
    print('jjjjjj')

if __name__ == '__main__':
    import inspect
    print(inspect.signature(get_name))
    x = inspect.getsource(get_name)
    print(x)
    print(get_name.__doc__)
    get_name()

现在装饰器变成定义一个函数的模样了,简单又直观,同时也处理了副作用,是一种理想的装饰器创建方式。

  • 使用wrapt模块的decorator
import wrapt
@wrapt.decorator
def six_decorator(func, instance, args, kwargs):
    res = func(*args, **kwargs)
    print(instance)
    print("do other something")
    return res

@six_decorator
def get_name(pas='fff'):
    """get_name function"""
    print('jjjjjj')

if __name__ == '__main__':
    import inspect
    print(inspect.signature(get_name))
    x = inspect.getsource(get_name)
    print(x)
    print(get_name.__doc__)
    get_name()
  • 说明:使用wrapt模块的decorator也可以很直观地实现装饰器,但是该装饰器的参数func, instance, args, kwargs是固定的;args, kwargs参数前面没有*号,instance在装饰器装饰类方法的时候可以得到该类的实例对象;func是被装饰的函数。

总结

  1. python的为了实现装饰模式,特别开发了@语法糖,通过闭包的方式实现装饰;

  2. 实际的项目中常用的装饰方法为自定义闭包或使用内置的decorator模块。

  • 作者:天宇之游
  • 出处:http://www.cnblogs.com/cwp-bg/
  • 本文版权归作者和博客园共有,欢迎转载、交流,但未经作者同意必须保留此段声明,且在文章明显位置给出原文链接。
原文地址:https://www.cnblogs.com/cwp-bg/p/9547797.html