Python 裝飾器使用非常地簡單。任何會使用 Python 函式的人都可以學習使用裝飾器:
1 2 3 |
@somedecorator def some_function(): print("Check it out, I'm using decorators!") |
但是如何寫裝飾器卻是另外一回事,而且這並不容易。你必須明白如下這些內容:
- 閉包
- 如何將函式做為一階引數
- 變數引數
- 引數解包
- 甚至是一些 Python 載入原始碼的細節
這些知識都需要花費大量的時間去理解掌握。如果你已經累積了許多其它需要學習的內容。這些內容還值得你去學習嗎?
對於我來說,答案是肯定的。希望你的答案也是肯定的。那麼,自己寫裝飾器的好處是什麼呢?或者說,它們會讓我在日常開發過程中哪些事變得容易?
分析、日誌與手段
對於大型應用, 我們常常需要記錄應用的狀態,以及測量不同活動的數量。通過將這些特別的事件包裝到函式或方法中,裝飾器可以很輕鬆地滿足這些需求,同時保證程式碼的可讀性。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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 異常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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 請求函式的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# 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) |
這裡有一個全域性物件 app,此物件有一個 route 方法。此 route 函式返回一個用於修飾請求處理函式的裝飾器。這背後的處理是非常複雜的,但是對於使用 Flask 的程式設計師來說,所有複雜的東西都被隱藏起來了。
在平時使用 Python 過程中,我們也會這樣使用裝飾器。例如,所有的物件都依賴於類方法與屬性裝飾器:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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 語句,但是每一個的語義都是不同的:
- 構造器是一個簡單的方法
- for_winter 是一個類方法
- progress 是一個只讀的動態屬性
@classmethod 裝飾器與 @property 裝飾器可以讓我們在平時使用過程中非常方便地擴充套件 Python 物件的語義。
複用不能複用的程式碼
Python 提供了非常強大的工具以將程式碼包裝成易複用的形式,這些工具包括:函式、函數語言程式設計的支援以及一切皆物件的思想。然而,還是存在某些程式碼並不能通過使用這些工具進行復用。
假設有一個古怪的 API。你可以通過 HTTP 傳送 JSON 格式的請求,它 99.9% 的情況下都是正確工作的。但是,小部分請求會返回伺服器內部錯誤的結果。這時候,你需要重新傳送請求。在這種情況下,你需要實現重試邏輯,像這樣:
1 2 3 4 5 6 7 8 |
resp = None while True: resp = make_api_call() if resp.status_code == 500 and tries < MAX_TRIES: tries += 1 continue break process_response(resp) |
現在假設你的程式碼庫中有很都地方都進行呼叫了函式 make_api_call,那麼是不是需要在每個呼叫的地方都實現這個 loop 迴圈呢?是不是每次新增一次呼叫都要實現一遍這個迴圈呢?這種模式能難有一個樣板程式碼,除非你使用裝飾器,那麼這就變得非常簡單了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# 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 的方式 。