python對裝飾器支持的非常好, 但是常見的python裝飾器并不代表整個裝飾者模式命满,它只是裝飾者模式的一種實現(xiàn)的表現(xiàn)耻蛇,模式一般試圖在宏觀上定義它的思想蜗侈。下面列出Python裝飾器的幾種寫法(這些代碼是我一位朋友收集的,經(jīng)過他同意我拿過來進行加工搬瑰,這是他的博客)
函數(shù)裝飾器
1. 函數(shù)不帶參數(shù)
# -.- coding:utf-8 -.-
def decorator(fun):
def wrapper():
print('裝飾器在這里動態(tài)的增加一條打印記錄.')
return fun()
return wrapper
@decorator
def hello_world():
_str = 'hello world!'
print(_str)
return _str
if __name__ == '__main__':
hello_world()
# 運行結(jié)果
裝飾器在這里動態(tài)的增加一條打印記錄.
hello world!
要點:
裝飾器會在對象加載的時候就執(zhí)行, 而不是在執(zhí)行階段才執(zhí)行款票。為了更直觀體現(xiàn), 這里在加一個代碼片段。
# -.- coding:utf-8 -.-
def decorator(fun):
print('這里是加載和檢查階段: 測試裝飾器是在執(zhí)行階段之前就已經(jīng)運行')
def wrapper():
print('裝飾器在這里動態(tài)的增加一條打印記錄.')
return fun()
return wrapper
print('這里是還沒檢查到decorator是一個裝飾器: 1')
@decorator
def hello_world():
_str = 'hello world!'
print(_str)
return _str
print('這里是已經(jīng)檢查到decorator是一個裝飾器: 2')
if __name__ == '__main__':
# 取消執(zhí)行這行代碼
# hello_world()
print('這里是執(zhí)行階段')
# 運行結(jié)果
這里是還沒檢查到decorator是一個裝飾器: 1
這里是加載和檢查階段: 測試裝飾器是在執(zhí)行階段之前就已經(jīng)運行
這里已經(jīng)檢查到decorator是一個裝飾器: 2
這里是執(zhí)行階段
備注:
仔細看上面的思維腦圖會發(fā)現(xiàn)加載了hello_world對象, 但是這個對象是實際上是一個decorator.<local>.wrapper
這種東西泽论,讓它看起來正常一些艾少,可以使用functools.wrap,這個不是很重要翼悴,我支持做個備注缚够。
# -.- coding:utf-8 -.-
import functools
def decorator(fun):
@functools.wraps(fun)
def wrapper():
print('裝飾器在這里動態(tài)的增加一條打印記錄.')
return fun()
return wrapper
@decorator
def hello_world():
_str = 'hello world!'
print(_str)
return _str
if __name__ == '__main__':
# 沒有加functools.wrap之前下面這行顯示出來的結(jié)果是: wrapper
# print(hello_world.__name__)
# 加了functools.wrap之后下面這行顯示出來的結(jié)果是: hello_world
print(hello_world.__name__)
# 運行結(jié)果
hello_world
2. 類不帶參數(shù)
# -.- coding:utf-8 -.-
import functools
def decorator(fun):
@functools.wraps(fun)
def wrapper():
print('裝飾器在這里動態(tài)的增加一條打印記錄.')
return fun()
return wrapper
@decorator
class HelloWorld(object):
def __init__(self):
self.message = 'hello world!'
print(self.message)
if __name__ == '__main__':
HelloWorld()
# 運行結(jié)果
裝飾器在這里動態(tài)的增加一條打印記錄.
hello world!
3. 函數(shù)帶參數(shù)
# -.- coding:utf-8 -.-
def decorator(fun):
def wrapper(*args, **kwargs): # add here
print('裝飾器在這里動態(tài)的增加一條打印記錄.')
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.")
# 運行結(jié)果
裝飾器在這里動態(tài)的增加一條打印記錄.
hello world!
I'm Learning Python.
4. 類帶參數(shù)
# -.- coding:utf-8 -.-
import functools
def decorator(fun):
@functools.wraps(fun)
def wrapper(*args, **kwargs): # 調(diào)整這里
print('裝飾器在這里動態(tài)的增加一條打印記錄.')
return fun(*args, **kwargs) # 調(diào)整這里
return wrapper
@decorator
class HelloWorld(object):
def __init__(self, message): # 調(diào)整這里
self.message = message # 調(diào)整這里
print(self.message)
if __name__ == '__main__':
HelloWorld('hello world!') # 調(diào)整這里
5. 裝飾器帶參數(shù)
# -.- coding:utf-8 -.-
def decorator(status_code, msg=None):
"""
:param status_code: 當傳過來的狀態(tài)碼是400-500區(qū)間時,
就拋出異常(不執(zhí)行裝飾對象行數(shù)).
: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, '沒有權(quán)限訪問')
def hello_world(name, age, message='Learning python'):
print('information: ', name, age, message)
if __name__ == '__main__':
hello_world('zt', '18', '學習Python')
# 運行結(jié)果
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: 沒有權(quán)限訪問
?
?
類裝飾器
函數(shù)不帶參數(shù)
# coding: utf-8
class Decorator(object):
def __init__(self, func):
print('類裝飾器在這里動態(tài)的增加一條打印記錄.')
self.func = func
def __call__(self):
self.func()
@Decorator
def hello_world():
_str = 'hello world!'
print(_str)
return _str
if __name__ == '__main__':
hello_world()
# 運行結(jié)果
類裝飾器在這里動態(tài)的增加一條打印記錄.
hello world!
備注:
還是語法原則,當檢查到@符號聲明裝飾器時,即便不運行任何代碼谍椅,python也會去執(zhí)行這個裝飾器误堡,并將這個hello_world
當做參數(shù)傳遞給這個裝飾器。
類不帶參數(shù)
# coding: utf-8
class Decorator(object):
def __init__(self, func):
print('類裝飾器在這里動態(tài)的增加一條打印記錄.')
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()
# 運行結(jié)果
類裝飾器在這里動態(tài)的增加一條打印記錄.
hello world!
函數(shù)帶參數(shù)
# coding: utf-8
class Decorator(object):
def __init__(self, func):
print('類裝飾器在這里動態(tài)的增加一條打印記錄.')
self.func = func
def __call__(self, *args, **kwargs): # 調(diào)整這里
self.func(*args, **kwargs) # 調(diào)整這里
@Decorator
def hello_world(message): # 調(diào)整這里
_str = message # 調(diào)整這里
print(_str)
return _str
if __name__ == '__main__':
hello_world('hello world!')
# 運行結(jié)果
類裝飾器在這里動態(tài)的增加一條打印記錄.
hello world!
類帶參數(shù)
# coding: utf-8
class Decorator(object):
def __init__(self, func):
print('類裝飾器在這里動態(tài)的增加一條打印記錄.')
self.func = func
def __call__(self, *args, **kwargs): # 調(diào)整這里
self.func(*args, **kwargs) # 調(diào)整這里
@Decorator
class HelloWorld(object):
def __init__(self, message): # 調(diào)整這里
self.message = message # 調(diào)整這里
print(self.message)
if __name__ == '__main__':
HelloWorld('hello world!')
# 運行結(jié)果
類裝飾器在這里動態(tài)的增加一條打印記錄.
hello world!
類裝飾器帶參數(shù)
# coding: utf-8
class Decorator(object):
def __init__(self, status_code, msg=None):
print('類裝飾器在這里動態(tài)的增加一條打印記錄.')
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='沒有權(quán)限訪問')
class HelloWorld(object):
def __init__(self, message):
self.message = message
print(self.message)
if __name__ == '__main__':
HelloWorld(message='hello world!')
# 運行結(jié)果
類裝飾器在這里動態(tài)的增加一條打印記錄.
Traceback (most recent call last):
File "C:/Users/xx/_10_classdecoratorwithargument_class_with_argument.py", line 17, in <module>
@Decorator(status_code=401, msg='沒有權(quán)限訪問')
File "C:/Users/xx/_10_classdecoratorwithargument_class_with_argument.py", line 9, in __init__
raise RuntimeError(msg)
RuntimeError: 沒有權(quán)限訪問
# 將status_code參數(shù)的值改為399, 再次運行
類裝飾器在這里動態(tài)的增加一條打印記錄.
hello world!
小結(jié)
上面就是所有的裝飾器辦法雏吭,你認為函數(shù)裝飾器和類裝飾器能去裝飾一個類中的某個方法嗎锁施? 要不自己試一下?
?
?
裝飾者模式
我閱讀的書籍是Head First設(shè)計模式思恐,書上的故事場景是圍繞星巴克的咖啡品種和調(diào)料來動態(tài)計算價格; 例如某種類型的咖啡底價是0.9美元,加一種調(diào)料需要相應(yīng)的增加一些價格膊毁,一杯咖啡可以隨意組合多種調(diào)料(也允許對某一種調(diào)料加多份)胀莹,最終計算出咖啡的價格。
代碼
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 = ", 調(diào)味品"
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())))
# 運行結(jié)果
星巴克意式濃縮咖啡 13.532
星巴克深度烘焙咖啡, 摩卡, 摩卡, 奶泡 10.132
星巴克首選咖啡, 豆奶, 摩卡, 奶泡 9.111999999999998
模式總結(jié)
如果采用python版本的裝飾器婚温,那么這個功能就很難寫的出來描焰,因為沒有利用到繼承、接口和組合的特性栅螟。
都是裝飾器荆秦,在應(yīng)對不同的場景是有不同的寫法,像python版本的裝飾器最好是用來解決單一的一次性目的(例如: 驗證力图、條件篩選)步绸;而OOP式的裝飾器就顯得特別靈活,能不能寫得好吃媒,寫的合理不合理瓤介,特別需要個人的功底積累。
核心理念
在不改變原有代碼的基礎(chǔ)上為其封裝額外的行為(wrap functionality with other functionality in order to affect outputs).
模式類型
結(jié)構(gòu)模式
設(shè)計原則
- 類應(yīng)該對擴展開放赘那,對修改關(guān)閉刑桑。
- 動態(tài)計算(或者叫累計計算)。
?
?
參考
- [x] Head First設(shè)計模式
- [x] Head First設(shè)計模式Python版源碼
- [x] patterns-idioms
- [x] py-patterns
- [x] enomine的博客