最近在閱讀Python微型Web框架Bottle的源碼,發現了Bottle中有一個既是裝飾器類又是描述符的有趣實現。剛好這兩個點是Python比較的難理解,又混合在一起,讓代碼有些晦澀難懂。但理解代碼之后不由得為Python語言的簡潔優美贊嘆。所以把相關知識和想法稍微整理,以供分享。
正文
Bottle是Python的一個微型Web框架,所有代碼都在一個bottle.py文件中,只依賴標準庫實現,兼容Python 2和Python 3,而且最新的穩定版0.12代碼也只有3700行左右。雖然小,但它實現了Web框架基本功能。這里就不以過多的筆墨去展示Bottle框架,需要的請訪問其網站了解更多。這里著重介紹與本文相關的重要對象request。在Bottle里,request對象代表了當前線程處理的請求,客戶端發送的請求數據如表單數據,請求網站和cookie都可以從request對象中獲得。下面是官方文檔中的兩個例子
from bottle import request, route, response, template
# 獲取客戶端cookie以實現登陸時問候用戶功能@route('/hello')def hello(): name = request.cookie.username or 'Guest' return template('Hello {{name}}', name=name) # 獲取形如/forum?id=1&page=5的查詢字符串中id和page變量的值route('/forum')def display_forum(): forum_id = request.query.id page = request.query.page or '1' return template('Forum ID: {{id}} (page {{page}})', id=forum_id, page=page)那么Bottle是如何實現的呢?根據WSGI接口規定,所有的HTTP請求信息都包含在一個名為envrion的dict對象中。所以Bottle要做的就是把HTTP請求信息從environ解析出來。在深入Request類如何實現之前先要了解下Bottle的FormsDict。FormsDict與字典類相似,但擴展了一些功能,比如支持屬性訪問、一對多的鍵值對、WTForms支持等。它在Bottle中被廣泛應用,如上面的示例中cookie和query數據都以FormsDict存儲,所以我們可以用request.query.page的方式獲取相應屬性值。
下面是0.12版Bottle中Request類的部分代碼,0.12版中Request類繼承了BaseRequest,為了方便閱讀我把代碼合并在一起,同時還有重要的DictProperty的代碼。需要說明的是Request類__init__傳入的environ參數就是WSGI協議中包含HTTP請求信息的envrion,而query方法中的_parse_qsl函數可以接受形如/forum?id=1&page=5原始查詢字符串然后以[(key1, value1), (ke2, value2), …]的list返回。
class DictProperty(object): """ Property that maps to a key in a local dict-like attribute. """ def __init__(self, attr, key=None, read_only=False): self.attr, self.key, self.read_only = attr, key, read_only def __call__(self, func): functools.update_wrapper(self, func, updated=[]) self.getter, self.key = func, self.key or func.__name__ return self def __get__(self, obj, cls): if obj is None: return self key, storage = self.key, getattr(obj, self.attr) if key not in storage: storage[key] = self.getter(obj) return storage[key] def __set__(self, obj, value): if self.read_only: raise AttributeError("Read-Only property.") getattr(obj, self.attr)[self.key] = value def __delete__(self, obj): if self.read_only: raise AttributeError("Read-Only property.") del getattr(obj, self.attr)[self.key]class Request: def __init__(self, environ=None): self.environ {} if environ is None else envrion self.envrion['bottle.request'] = self @DictProperty('environ', 'bottle.request.query', read_only=True) def query(self): get = self.environ['bottle.get'] = FormsDict() pairs = _parse_qsl(self.environ.get('QUERY_STRING', '')) for key, value in pairs: get[key] = value return get
新聞熱點
疑難解答