炼数成金 门户 大数据 Python 查看内容

设计模式03装饰者模式

2017-8-10 14:49| 发布者: 炼数成金_小数| 查看: 16002| 评论: 0|原作者: 极光火狐狸|来自: 简书

摘要: 如果采用python版本的装饰器,那么这个功能就很难写的出来,因为没有利用到继承、接口和组合的特性。都是装饰器,在应对不同的场景是有不同的写法,像python版本的装饰器最好是用来解决单一的一次性目的(例如: 验证 ...

Python 测试

python对装饰器支持的非常好, 但是常见的python装饰器并不代表整个装饰者模式,它只是装饰者模式的一种实现的表现,模式一般试图在宏观上定义它的思想。下面列出Python装饰器的几种写法(这些代码是我一位朋友收集的,经过他同意我拿过来进行加工,这是他的博客)

函数装饰器
1. 函数不带参数

# -.- coding:utf-8 -.-

def decorator(fun):

    def wrapper():
        print('装饰器在这里动态的增加一条打印记录.')
        return fun()

    return wrapper

@decorator
def hello_world():
    _str = 'hello world!'
    print(_str)
    return _str

if __name__ == '__main__':
    hello_world()


# 运行结果
装饰器在这里动态的增加一条打印记录.
hello world!

装饰器内部原理

要点:
装饰器会在对象加载的时候就执行, 而不是在执行阶段才执行。为了更直观体现, 这里在加一个代码片段。

# -.- coding:utf-8 -.-


def decorator(fun):

    print('这里是加载和检查阶段: 测试装饰器是在执行阶段之前就已经运行')

    def wrapper():
        print('装饰器在这里动态的增加一条打印记录.')
        return fun()

    return wrapper


print('这里是还没检查到decorator是一个装饰器: 1')
@decorator
def hello_world():
    _str = 'hello world!'
    print(_str)
    return _str
print('这里是已经检查到decorator是一个装饰器: 2')

if __name__ == '__main__':
    # 取消执行这行代码
    # hello_world()

    print('这里是执行阶段')

# 运行结果
这里是还没检查到decorator是一个装饰器: 1
这里是加载和检查阶段: 测试装饰器是在执行阶段之前就已经运行
这里已经检查到decorator是一个装饰器: 2
这里是执行阶段
备注:
仔细看上面的思维脑图会发现加载了hello_world对象, 但是这个对象是实际上是一个decorator.<local>.wrapper这种东西,让它看起来正常一些,可以使用functools.wrap,这个不是很重要,我支持做个备注。

# -.- coding:utf-8 -.-
import functools


def decorator(fun):

    @functools.wraps(fun)
    def wrapper():
        print('装饰器在这里动态的增加一条打印记录.')
        return fun()

    return wrapper

@decorator
def hello_world():
    _str = 'hello world!'
    print(_str)
    return _str

if __name__ == '__main__':
    # 没有加functools.wrap之前下面这行显示出来的结果是: wrapper
    # print(hello_world.__name__)

    # 加了functools.wrap之后下面这行显示出来的结果是: hello_world
    print(hello_world.__name__)


# 运行结果
hello_world

2. 类不带参数
# -.- coding:utf-8 -.-
import functools

def decorator(fun):

    @functools.wraps(fun)
    def wrapper():
        print('装饰器在这里动态的增加一条打印记录.')
        return fun()

    return wrapper


@decorator
class HelloWorld(object):

    def __init__(self):
        self.message = 'hello world!'
        print(self.message)

if __name__ == '__main__':
    HelloWorld()


# 运行结果
装饰器在这里动态的增加一条打印记录.
hello world!

3. 函数带参数
# -.- coding:utf-8 -.-

def decorator(fun):

    def wrapper(*args, **kwargs):                   # add here
        print('装饰器在这里动态的增加一条打印记录.')
        return fun(*args, **kwargs)                 # add here

    return wrapper

@decorator
def hello_world(introduce):                         # add here
    _str = 'hello world!\n{}'.format(introduce)     # invoke here       
    print(_str)
    return _str

if __name__ == '__main__':
    hello_world("I'm Learning Python.")


# 运行结果
装饰器在这里动态的增加一条打印记录.
hello world!
I'm Learning Python.

4. 类带参数
# -.- coding:utf-8 -.-
import functools


def decorator(fun):

    @functools.wraps(fun)
    def wrapper(*args, **kwargs):           # 调整这里
        print('装饰器在这里动态的增加一条打印记录.')
        return fun(*args, **kwargs)         # 调整这里

    return wrapper


@decorator
class HelloWorld(object):

    def __init__(self, message):            # 调整这里
        self.message = message              # 调整这里
        print(self.message) 

if __name__ == '__main__':
    HelloWorld('hello world!')           # 调整这里
5. 装饰器带参数

# -.- coding:utf-8 -.-


def decorator(status_code, msg=None):
    """

    :param status_code: 当传过来的状态码是400-500区间时, 
                         就抛出异常(不执行装饰对象行数). 
    :param msg: 抛异常时显示的信息.
    :return: 
    """
    def wrapper_(fun):
        def wrapper__(*args, **kwargs):
            if 400 <= status_code <= 500:
                raise RuntimeError(msg)
            fun(*args, **kwargs)
        return wrapper__

    return wrapper_


@decorator(401, '没有权限访问')
def hello_world(name, age, message='Learning python'):
    print('information: ', name, age, message)


if __name__ == '__main__':
    hello_world('zt', '18', '学习Python')


# 运行结果
Traceback (most recent call last):
  File "C:/Users/xx/_03_decorator_with_arguments.py", line 29, in <module>
    hello_world('zt', '18', '学习Python')
  File "C:/Users/xx/_03_decorator_with_arguments.py", line 16, in wrapper__
    raise RuntimeError(msg)
RuntimeError: 没有权限访问

类装饰器

函数不带参数

# coding: utf-8

class Decorator(object):

    def __init__(self, func):
        print('类装饰器在这里动态的增加一条打印记录.')
        self.func = func

    def __call__(self):
        self.func()

@Decorator
def hello_world():
    _str = 'hello world!'
    print(_str)
    return _str

if __name__ == '__main__':
    hello_world()

# 运行结果
类装饰器在这里动态的增加一条打印记录.
hello world!

备注:
还是语法原则,当检查到@符号声明装饰器时,即便不运行任何代码,python也会去执行这个装饰器,并将这个hello_world当做参数传递给这个装饰器。

类不带参数
# coding: utf-8

class Decorator(object):

    def __init__(self, func):
        print('类装饰器在这里动态的增加一条打印记录.')
        self.func = func

    def __call__(self):
        self.func()


@Decorator
class HelloWorld(object):

    def __init__(self):
        self.message = 'hello world!'
        print(self.message)

if __name__ == '__main__':
    HelloWorld()


# 运行结果
类装饰器在这里动态的增加一条打印记录.
hello world!

函数带参数
# coding: utf-8

class Decorator(object):

    def __init__(self, func):
        print('类装饰器在这里动态的增加一条打印记录.')
        self.func = func

    def __call__(self, *args, **kwargs):        # 调整这里
        self.func(*args, **kwargs)              # 调整这里


@Decorator
def hello_world(message):                       # 调整这里
    _str = message                              # 调整这里
    print(_str)
    return _str


if __name__ == '__main__':
    hello_world('hello world!')


# 运行结果
类装饰器在这里动态的增加一条打印记录.
hello world!

类带参数
# coding: utf-8

class Decorator(object):

    def __init__(self, func):
        print('类装饰器在这里动态的增加一条打印记录.')
        self.func = func

    def __call__(self, *args, **kwargs):            # 调整这里
        self.func(*args, **kwargs)                   # 调整这里


@Decorator
class HelloWorld(object):

    def __init__(self, message):                    # 调整这里
        self.message = message                      # 调整这里
        print(self.message)

if __name__ == '__main__':
    HelloWorld('hello world!')


# 运行结果
类装饰器在这里动态的增加一条打印记录.
hello world!

类装饰器带参数
# coding: utf-8

class Decorator(object):

    def __init__(self, status_code, msg=None):
        print('类装饰器在这里动态的增加一条打印记录.')
        if 400 <= status_code <= 500:
            raise RuntimeError(msg)

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper

@Decorator(status_code=401, msg='没有权限访问')
class HelloWorld(object):

    def __init__(self, message):
        self.message = message
        print(self.message)

if __name__ == '__main__':
    HelloWorld(message='hello world!')

# 运行结果
类装饰器在这里动态的增加一条打印记录.
Traceback (most recent call last):
  File "C:/Users/xx/_10_classdecoratorwithargument_class_with_argument.py", line 17, in <module>
    @Decorator(status_code=401, msg='没有权限访问')
  File "C:/Users/xx/_10_classdecoratorwithargument_class_with_argument.py", line 9, in __init__
    raise RuntimeError(msg)
RuntimeError: 没有权限访问

# 将status_code参数的值改为399, 再次运行
类装饰器在这里动态的增加一条打印记录.
hello world!

小结
上面就是所有的装饰器办法,你认为函数装饰器和类装饰器能去装饰一个类中的某个方法吗? 要不自己试一下?

装饰者模式
我阅读的书籍是Head First设计模式,书上的故事场景是围绕星巴克的咖啡品种和调料来动态计算价格; 例如某种类型的咖啡底价是0.9美元,加一种调料需要相应的增加一些价格,一杯咖啡可以随意组合多种调料(也允许对某一种调料加多份),最终计算出咖啡的价格。

代码
from __future__ import print_function

class DollarToRMB(object):

    @staticmethod
    def convert(dollar):
        return dollar * 6.8

################################################################################
# Beverages                                                                    #
################################################################################
class Beverage(object):

    def __init__(self):
        self._description = '饮料'
        self._price = 0

    @property
    def description(self):
        return self._description

    @property
    def price(self):
        return DollarToRMB.convert(self._price)

    def cost(self):
        raise NotImplementedError


class HouseBlend(Beverage):

    def __init__(self):
        super(HouseBlend, self).__init__()
        self._description = "星巴克推荐咖啡"
        self._price = 0.89

    def cost(self):
        return self.price

class DarkRoast(Beverage):

    def __init__(self):
        super(DarkRoast, self).__init__()
        self._description = "星巴克深度烘焙咖啡"
        self._price = .99

    def cost(self):
        return self.price

class Espresso(Beverage):

    def __init__(self):
        super(Espresso, self).__init__()
        self._description = "星巴克意式浓缩咖啡"
        self._price = 1.99

    def cost(self):
        return self.price


class Decaf(Beverage):

    def __init__(self):
        super(Decaf, self).__init__()
        self._description = "星巴克不含咖啡因的咖啡"
        self._price = 1.05

    def cost(self):
        return self.price


################################################################################
# Condiment decorators                                                         #
################################################################################
class CondimentDecorator(Beverage):

    def __init__(self, beverage):
        super(CondimentDecorator, self).__init__()
        self._description = ", 调味品"
        self._price = 0
        self.beverage = beverage

    def description(self):
        raise NotImplementedError

    def cost(self):
        raise NotImplementedError


class Milk(CondimentDecorator):

    def __init__(self, beverage):
        super(Milk, self).__init__(beverage)
        self._description = ", 牛奶"
        self._price = .10

    @property
    def description(self):
        return self.beverage.description + self._description

    def cost(self):
        return self.price + self.beverage.cost()


class Mocha(CondimentDecorator):

    def __init__(self, beverage):
        super(Mocha, self).__init__(beverage)
        self._description = ", 摩卡"
        self._price = .20

    @property
    def description(self):
        return self.beverage.description + self._description

    def cost(self):
        return self.price + self.beverage.cost()


class Soy(CondimentDecorator):

    def __init__(self, beverage):
        super(Soy, self).__init__(beverage)
        self._description = ", 豆奶"
        self._price = .15

    @property
    def description(self):
        return self.beverage.description + self._description

    def cost(self):
        return self.price + self.beverage.cost()


class Whip(CondimentDecorator):

    def __init__(self, beverage):
        super(Whip, self).__init__(beverage)
        self._description = ", 奶泡"
        self._price = .10

    @property
    def description(self):
        return self.beverage.description + self._description

    def cost(self):
        return self.price + self.beverage.cost()


if __name__ == '__main__':
    beverage = Espresso()
    print('{!s:<50}{}'.format(beverage.description, str(beverage.cost())))

    beverage2 = DarkRoast()
    beverage2 = Mocha(beverage2)
    beverage2 = Mocha(beverage2)
    beverage2 = Whip(beverage2)
    print('{!s:<50}{}'.format(beverage2.description, str(beverage2.cost())))

    beverage3 = HouseBlend()
    beverage3 = Soy(beverage3)
    beverage3 = Mocha(beverage3)
    beverage3 = Whip(beverage3)
    print('{!s:<50}{}'.format(beverage3.description,  str(beverage3.cost())))


# 运行结果
星巴克意式浓缩咖啡   13.532
星巴克深度烘焙咖啡, 摩卡, 摩卡, 奶泡   10.132
星巴克推荐咖啡, 豆奶, 摩卡, 奶泡    9.111999999999998

模式总结
如果采用python版本的装饰器,那么这个功能就很难写的出来,因为没有利用到继承、接口和组合的特性。
都是装饰器,在应对不同的场景是有不同的写法,像python版本的装饰器较好是用来解决单一的一次性目的(例如: 验证、条件筛选);而OOP式的装饰器就显得特别灵活,能不能写得好,写的合理不合理,特别需要个人的功底积累。

核心理念
在不改变原有代码的基础上为其封装额外的行为(wrap functionality with other functionality in order to affect outputs).

模式类型
结构模式

设计原则
类应该对扩展开放,对修改关闭。
动态计算(或者叫累计计算)。

参考
[x] Head First设计模式
[x] Head First设计模式Python版源码
[x] patterns-idioms
[x] py-patterns
[x] enomine的博客

欢迎加入本站公开兴趣群
软件开发技术群
兴趣范围包括:Java,C/C++,Python,PHP,Ruby,shell等各种语言开发经验交流,各种框架使用,外包项目机会,学习、培训、跳槽等交流
QQ群:26931708

Hadoop源代码研究群
兴趣范围包括:Hadoop源代码解读,改进,优化,分布式系统场景定制,与Hadoop有关的各种开源项目,总之就是玩转Hadoop
QQ群:288410967 

鲜花

握手

雷人

路过

鸡蛋

相关阅读

最新评论

热门频道

  • 大数据
  • 商业智能
  • 量化投资
  • 科学探索
  • 创业

即将开课

 

GMT+8, 2018-6-26 01:51 , Processed in 0.198497 second(s), 25 queries .