## Python 裝飾器需要了解的知識
* 閉包
* 如何將函數做為一階參數
* 變量參數
* 參數解包
* Python 加載源代碼的細節
## 分析、日志與手段
對于大型應用, 我們常常需要**記錄應用的狀態**,以及測量不同活動的數量。通過將這些特別的事件包裝到函數或方法中,裝飾器可以很輕松地滿足這些需求,同時保證代碼的可讀性。
```
from myapp.log import logger
def log_order_event(func):
def wrapper(*args, **kwargs):
logger.info("Ordering: ", func.__name__)
order = func(*args, **kwargs)
logger.debug("Order result: ", 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 異常:
```
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 請求函數的:
```
# 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 過程中,我們也會這樣使用裝飾器。例如,所有的對象都依賴于類方法與屬性裝飾器:
```
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% 的情況下都是正確工作的。但是,小部分請求會返回服務器內部錯誤的結果。這時候,你需要重新發送請求。在這種情況下,你需要實現重試邏輯,像這樣:
```
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 循環呢?是不是每次添加一次調用都要實現一遍這個循環呢?這種模式能難有一個樣板代碼,除非你使用裝飾器,那么這就變得非常簡單了:
```
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
@retry
def make_api_call():
# ....
```
## 讓你的事業騰飛
剛開始寫裝飾器時可能不是那么容易。雖然這并不像造火箭那么難,但你也需要花費一些時間去學習,掌握其中的奧秘。大部分程序都能夠掌握。當你成為團隊里面能把裝飾器寫得很好并且能解決真正的問題的人時,此時其它開發者都會使用你開發的這些裝飾器。因為一旦最難的部分,也就是實現裝飾器完成后,使用裝飾器是非常容易的。這可以極大的放大你所寫代碼的正面影響,這會讓你成為團隊的英雄。
- 前言
- 環境搭建
- pypi
- 打包
- Python 2 和 Python 3 的版本之間差別
- 項目
- 第一部分
- 第1章 基礎
- Python安裝
- python代碼文件類型
- python對象
- 核心數據類型
- 核心數據類型--整型和浮點型
- 核心數據類型--字符串
- str.format
- 核心數據類型--列表
- 核心數據類型--元組
- 核心數據類型--字典
- 核心數據類型--集合
- 核心數據類型--文件對象
- 調用bash
- 標準輸入輸出
- str-repr
- 字符編碼
- 迭代器和生成器
- 第2章 語句和語法
- 賦值語句
- if語句
- while語句
- for語句
- assert
- 第3章 函數
- 函數作用域
- 工廠函數
- 內置函數
- 遞歸
- 嵌套作用域和lambda
- 參數傳遞
- 函數式編程
- property可寫與可讀
- 第5章 模塊
- 模塊導入
- 模塊命名空間
- 相對導入和絕對導入
- 模塊重載
- 在模塊中隱藏數據
- 過渡性重載
- 第6章 類
- 面向對象還是面向過程?
- 構造函數 析構函數
- call
- 運算符重載
- str()
- 待定
- 即時生成屬性
- 多態
- 線程和進程
- thread模塊
- threading模塊
- threading線程鎖
- 糖果機
- multiprocessing
- 阻塞非阻塞同步異步
- 單線程和多線程對比
- 生產者消費者模型
- 第二部分
- 獲取系統資源信息
- 獲取進程所占的物理內存
- dmidecode獲取系統信息
- 網絡編程
- 網絡基礎
- python中的套接字
- socket模塊
- 第三部分 高級功能
- 閉包入門
- 閉包的應用
- 裝飾器入門
- 裝飾器應用
- 第四部分 項目實戰
- graphite
- 模塊
- collections
- datetime
- Enum
- faker
- fabric
- fileinput
- fire
- fnmatch
- getpass
- glob
- hashlib
- heapq
- json模塊
- log
- os
- Paramiko
- parser
- platform
- pyyaml
- Queue
- random
- re
- 特殊符號和字符
- re模塊
- shelves
- subprocess
- time
- urllib_urllib2_requests
- urllib urllib2
- requests
- 標準模塊ConfigParser
- 擴展模塊Mysqldb
- 擴展模塊dns
- 擴展模塊request
- uuid
- cacheout 緩存庫
- delorean 時間
- 附錄
- 內置函數
- python實現各種排序算法
- 常見報錯
- pymongo
- pyrocksdb
- 常用
- ERROR