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.
这种东西,让它看起来正常一些,可以使用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
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
@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的博客