標籤:func cti 對象 ring 中繼資料 內省 自己的 總結 機器
裝飾器通常是一個命名的對象(不允許 lambda 運算式), 在被(裝飾函數)調用時接受單一參數, 並返回另一個可調用對象. 這裡的可調用對象, 不僅僅包含函數和方法, 還包括類. 任何可調用對象(任何實現了 call 方法的對象都是可調用的)都可用作裝飾器, 他們返回的對象也不是簡單的函數, 而是實現了自己的 call 方法的更複雜的類執行個體.
@some_decoratordef decorated_function(): pass# 以上寫法總是可以替換為顯式的裝飾器調用和函數的重新賦值:decorated_function = some_decorator(decorated_function)
1. 裝飾器定義/使用方法1.1 通用模式: 作為一個函數
def mydecorator(function): def wrapped(*args, **kwargs): # 在函數調用之前, 做點什麼 result = function(*args, **kwargs) # 在函數調用之後, 做點什麼 # 返回結果 return result # 返回 wrapper 作為裝飾函數 return wrapped
1.2 實現
call 方法: 作為一個類
非參數化裝飾器用作類的通用模式如下:
class DecoratorAsClass: def __init__(self, function): self.function = function def __call__(self, *args, **kw): # 在調用原始函數之前, 做點什麼 result = self.function(*args, **kwargs) # 在調用原始函數之後, 做點什麼 # 返回結果 return result
1.3 參數化裝飾器 : 實現第二層封裝
def repeat(number=3): """ 多次重複執行裝飾函數, 返回最後一次原始函數調用的值作為結果. : param number: 重複次數, 預設值為 3 """ def actual_decorator(function): def wrapped(*args, **kwargs): result = None for _ in range(number): result = function(*args, **kwargs) return result return wrapped return actual_decorator@repeat(2)def foo(): print("foo")
帶參數的裝飾器總是可以做如下裝換:
foo = repeat(number=3)(foo)
即使參數化裝飾器的參數有預設值, 但名字後面也必須加括弧
@repeat()def bar(): print("bar")
1.4 儲存內省的裝飾器
使用裝飾器的常見缺點是: 使用裝飾器時, 不儲存函數中繼資料(主要是文檔字串和原始函數名). 裝飾器組合建立了一個新函數, 並返回一個新對象, 完全沒有考慮原函數的標誌. 這將導致調試裝飾器裝飾過的函數更加困難, 也會破壞可能用到的大多數自動生產文檔的工具, 應為無法訪問原始的文檔字串和函數簽名.
解決這個問題的方式, 就是使用 functools 模組內建的 wraps() 裝飾器.
from functools import wrapsdef preserving_decorator(function): @wraps(function) def wrapped(*args, **kwargs): """封裝函數內部文檔""" return function(*args, **kwargs) return wrapped@preserving_decoratordef function_with_important_docstring(): """這是我們想要儲存的文檔字串""" passprint(function_with_important_docstring.__name__)print(function_with_important_docstring.__doc__)
2. 裝飾器常用樣本2.1 參數檢查
檢查函數接受或返回的參數, 在特定上下文中執行時可能有用.
# 裝飾器代碼rpc_info = {} # 在實際讀取時, 這個類定義會填充 rpc_info 字典, 並用於檢查參數類型的特定環境中.def xmlrpc(in_=(), out=(type(None), )): def _xmlrpc(function): # 註冊簽名 func_name = function.__name__ rpc_info[func_name] = (in_, out) def _check_types(elements, types): """用來檢查類型的子函數""" if len(elements) != len(types): raise TypeError("Argumen count is wrong") typed = enumerate(zip(elements, types)) for index, couple in typed: arg, of_the_right_type = couple if isinstance(arg, of_the_right_type): continue raise TypeError("Arg #%d should be %s" % (index, of_the_right_type)) def __xmlrpc(*args): # 沒有允許的關鍵詞 # 檢查輸入的內容 if function.__class__ == "method": checkable_args = args[1:] # 類方法, 去掉 self else: checkable_args = args[:] # 普通函數 _check_types(checkable_args, in_) # 運行函數 res = function(*args) # 檢查輸入內容 if not type(res) in (tuple, list): checkable_res = (res, ) else: checkable_res = res _check_types(checkable_res, out) # 函數機器類型檢查成功 return res return __xmlrpc return _xmlrpc# 使用樣本class RPCView: @xmlrpc((int, int)) # two int --> None def meth1(self, int1, int2): print("received %d and %d" % (int1, int2)) @xmlrpc((str, ), (int, )) # string --> int def meth2(self, phrase): print("received %s" % phrase) return 12# 調用輸出print(rpc_info) # 輸出:# {'meth1': ((<class 'int'>, <class 'int'>), (<class 'NoneType'>,)), 'meth2': ((<class 'str'>,), (<class 'int'>,))}my = RPCView()my.meth1(1, 2)# 輸出: 類型檢查成功# received 1 and 2my.meth2(2)# 輸出: 類型檢查失敗# File "D:\VBoxShare\Work\Documents\PyProject\PyCookbook\test.py", line 57, in <module># my.meth2(2)# File "D:\VBoxShare\Work\Documents\PyProject\PyCookbook\test.py", line 25, in __xmlrpc# _check_types(checkable_args, in_)# File "D:\VBoxShare\Work\Documents\PyProject\PyCookbook\test.py", line 20, in _check_types# raise TypeError("Arg #%d should be %s" % (index, of_the_right_type))# TypeError: Arg #0 should be <class 'str'>
2.2 緩衝
緩衝裝飾器與參數檢查十分相似, 不過他重點是關注那些內容狀態不會影響輸入的函數, 每組參數都可以連結到唯一的結果. 因此, 緩衝裝飾器可以將輸出與計演算法所需的參數放在一起, 並在後續的調用中直接返回他(這種行為成為 memoizing).
import timeimport hashlibimport picklecache = {}def is_obsolete(entry, duration): return time.time() - entry["time"] > durationdef compute_key(function, args, kw): """ 利用已排序的參數來構建 SHA 雜湊鍵, 並將結果儲存在一個全域字典中. 利用 pickle 來建立 hash , 這是凍結所有作為參數傳入的對象狀態的捷徑, 以確保所有參數都滿足於要求. """ key = pickle.dumps((function.__name__, args, kw)) return hashlib.sha1(key).hexdigest()def memoize(duration=10): def _memoize(function): def __memoize(*args, **kw): key = compute_key(function, args, kw) # 是否已經擁有它了? if (key in cache and not is_obsolete(cache[key], duration)): print("We got a winner.") return cache[key]["value"] # 計算 result = function(*args, **kw) # 儲存結果 cache[key] = { "value": result, "time": time.time() } return result return __memoize return _memoize@memoize()def func_1(a, b): return a + bprint(func_1(2, 2)) # 4print(func_1(2, 2)) # print , 4@memoize(1)def func_2(a, b): return a + bprint(func_2(2, 2)) # 4time.sleep(1)print(func_2(2, 2)) # 4
緩衝值還可以與函數本身綁定, 以管理其範圍和生命週期, 代替集中化的字典. 但在任何情況下, 更高效的裝飾器會使用基於進階緩衝演算法的專用緩衝庫.
2.3 代理
代理裝飾器使用全域代理來標記和註冊函數. 例如, 一個根據目前使用者來保護代碼訪問的安全層可以使用集中式檢查器和相關的可調用對象要求的許可權來實現.
class User: def __init__(self, roles): self.roles = rolesclass Unauthorized(Exception): passdef protect(role): def _protect(function): def __protect(*args, **kw): user = globals().get("user") if user is None or role not in user.roles: raise Unauthorized("I won't tell you.") return function(*args, **kw) return __protect return _protecttarek = User(("admin", "user")) bill = User(("user",))class MySecrets: @protect("admin") def waffle_recipe(self): print("use tons of butter") these_are = MySecrets()user = tarekthese_are.waffle_recipe() # use tons of butteruser = billthese_are.waffle_recipe() # __main__.Unauthorized: I won't tell you.
以上模型常用於 Python Web 架構中(許可權驗證), 用於定義可發布類的安全性. 例如, Django 提供裝飾器來保護函數訪問的安全.
2.4 上下文提供者
上下文裝飾器確保函數可以運行在正確的上下文中, 或者在函數前後運行一些代碼, 換句話說, 他設定並複位一個特定的執行環境.
例如, 當一個資料項目需要在多個線程之間共用時, 就要用一個鎖來保護她避免多次訪問, 這個鎖可以在裝飾器中編寫.
from threading import RLocklock = RLock()def synchronized(function): def _synchronized(*args, **kw): lock.acquire() try: return function(*args, **kw) finally: lock.release() return _synchronized@synchronizeddef thread_safe(): # 確保鎖定資源 pass
上下裝飾器通常會被上下文管理器(with) 替代.
python 裝飾器總結