Python 裝飾器使用很是地簡單。任何會使用 Python 函數的人均可以學習使用裝飾器:python
Python程序員
@somedecorator def some_function(): print("Check it out, I'm using decorators!")
閉包可是如何寫裝飾器倒是另一回事,並且這並不容易。你必須明白以下這些內容:編程
如何將函數作爲一階參數json
變量參數api
這些知識都須要花費大量的時間去理解掌握。若是你已經累積了許多其它須要學習的內容。這些內容還值得你去學習嗎?服務器
對於我來講,答案是確定的。但願你的答案也是確定的。那麼,本身寫裝飾器的好處是什麼呢?或者說,它們會讓我在平常開發過程當中哪些事變得容易?閉包
分析、日誌與手段app
對於大型應用, 咱們經常須要記錄應用的狀態,以及測量不一樣活動的數量。經過將這些特別的事件包裝到函數或方法中,裝飾器能夠很輕鬆地知足這些需求,同時保證代碼的可讀性。框架
Pythonide
from myapp.log import logger def log_order_event(func): def wrapper(*args, **kwargs): logger.info("Ordering: %s", func.__name__) order = func(*args, **kwargs) logger.debug("Order result: %s", order.result) return order return wrapper @log_order_event def order_pizza(*toppings): # let's get some pizza!
驗證以及運行時檢查這個方法也能夠用來記數或者記錄其它某些指標。
Python 是一種強類型語言,可是變量的類型倒是動態變化的。雖然這會帶來不少好處,可是同時這也意味着更容易引入 bug。對於靜態語言,例如 Java, 這些 bug 在編譯階段就能夠被發現。於是,你可能但願在對傳入或返回的數據進行一些自定義的的檢查。裝飾器就可讓你很是容易地實現這個需求,並一次性將其應用到多個函數上。
想像一下:你有許多函數,每一個函數返回一個字典類型,該字典包含一個「summary 」域。這個域的值不能超過 80 個字符的長度。若是違反這個要求,那就是一個錯誤。下面這個裝飾器會在錯誤發生時拋出 ValueError 異常:
Python
def validate_summary(func): def wrapper(*args, **kwargs): data = func(*args, **kwargs) if len(data["summary"]) > 80: raise ValueError("Summary too long") return data return wrapper @validate_summary def fetch_customer_data(): # ... @validate_summary def query_orders(criteria): # ... @validate_summary def create_invoice(params): # ...
一旦你掌握瞭如何寫裝飾器,你就可以從其使用的簡單的語法中獲益頗豐,你能夠爲語言添加新的語義使其使用更加簡單。接下來最棒的就是你能夠本身擴展 Python 語法。建立框架
事實上,不少開源框架都是使用的這樣的方式。 Web 應用框架 Flask 就是使用裝飾器將不一樣 URL 路由到不一樣處理 HTTP 請求函數的:
Python
# For a RESTful todo-list API. @app.route("/tasks/", methods=["GET"]) def get_all_tasks(): tasks = app.store.get_all_tasks() return make_response(json.dumps(tasks), 200) @app.route("/tasks/", methods=["POST"]) def create_task(): payload = request.get_json(force=True) task_id = app.store.create_task( summary = payload["summary"], description = payload["description"], ) task_info = {"id": task_id} return make_response(json.dumps(task_info), 201) @app.route("/tasks/<int:task_id>/") def task_details(task_id): task_info = app.store.task_details(task_id) if task_info is None: return make_response("", 404) return json.dumps(task_info)
在平時使用 Python 過程當中,咱們也會這樣使用裝飾器。例如,全部的對象都依賴於類方法與屬性裝飾器:這裏有一個全局對象 app,此對象有一個 route 方法。此 route 函數返回一個用於修飾請求處理函數的裝飾器。這背後的處理是很是複雜的,可是對於使用 Flask 的程序員來講,全部複雜的東西都被隱藏起來了。
Python
class WeatherSimulation: def __init__(self, **params): self.params = params @classmethod def for_winter(cls, **other_params): params = {'month': 'Jan', 'temp': '0'} params.update(other_params) return cls(**params) @property def progress(self): return self.completed_iterations() / self.total_iterations()
構造器是一個簡單的方法這個類有三個不一樣的 def 語句,可是每個的語義都是不一樣的:
@classmethod 裝飾器與 @property 裝飾器可讓咱們在平時使用過程當中很是方便地擴展 Python 對象的語義。
複用不能複用的代碼
Python 提供了很是強大的工具以將代碼包裝成易複用的形式,這些工具包括:函數、函數式編程的支持以及一切皆對象的思想。然而,仍是存在某些代碼並不能經過使用這些工具進行復用。
假設有一個古怪的 API。你能夠經過 HTTP 發送 JSON 格式的請求,它 99.9% 的狀況下都是正確工做的。可是,小部分請求會返回服務器內部錯誤的結果。這時候,你須要從新發送請求。在這種狀況下,你須要實現重試邏輯,像這樣:
Python
resp = None while True: resp = make_api_call() if resp.status_code == 500 and tries < MAX_TRIES: tries += 1 continue break process_response(resp)
Python如今假設你的代碼庫中有很都地方都進行調用了函數 make_api_call,那麼是否是須要在每一個調用的地方都實現這個 loop 循環呢?是否是每次添加一次調用都要實現一遍這個循環呢?這種模式能難有一個樣板代碼,除非你使用裝飾器,那麼這就變得很是簡單了:
# The decorated function returns a Response object, # which has a status_code attribute. 200 means # success; 500 indicates a server-side error. def retry(func): def retried_func(*args, **kwargs): MAX_TRIES = 3 tries = 0 while True: resp = func(*args, **kwargs) if resp.status_code == 500 and tries < MAX_TRIES: tries += 1 continue break return resp return retried_func This gives you an easy-to-use @retry decorator: @retry def make_api_call(): # ....
剛開始寫裝飾器時可能不是那麼容易。雖然這並不像造火箭那麼難,但你也須要花費一些時間去學習,掌握其中的奧祕。大部分程序都可以掌握。當你成爲團隊裏面能把裝飾器寫得很好而且能解決真正的問題的人時,此時其它開發者都會使用你開發的這些裝飾器。由於一旦最難的部分,也就是實現裝飾器完成後,使用裝飾器是很是容易的。這能夠極大的放大你所寫代碼的正面影響,這會讓你成爲團隊的英雄。讓你的事業騰飛
我已經培訓了成百上千的軟件工程師讓他們更高效地使用 Python,這些團隊一致的反映裝飾器是這其中最有價值也是他們在 Python 高階編程中使用的最重要的工具。這也是爲何其成爲了接下來的 Python 課程重點:在 2016 年 5月 25日與 26 日基礎在線課程以後。
無論你是以何種方式學習實現裝飾器,你都會因其能完成的工做而感到興奮。毫無疑問,它能永久地改變你使用 Python 的方式 。