1. 程式人生 > >Bottle 框架中的裝飾器類和描述符應用

Bottle 框架中的裝飾器類和描述符應用

最近在閱讀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

Python
123456789101112 # 獲取客戶端cookie以實現登陸時問候使用者功能@route('/hello')defhello():name=request.cookie.username or'Guest'returntemplate('Hello {{name}}',name=name)# 獲取形如/forum?id=1&page=5的查詢字串中id和page變數的值route('/forum')defdisplay_forum():forum_id=request.query.idpage=request
.query.page or'1'returntemplate('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返回。

Python
12345678910111213141516171819202122232425262728293031323334353637 classDictProperty(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_onlydef__call__(self,func):functools.update_wrapper(self,func,updated=[])self.getter,self.key=func,self.key orfunc.__name__returnselfdef__get__(self,obj,cls):ifobj isNone:returnselfkey,storage=self.key,getattr(obj,self.attr)ifkey notinstorage:storage[key]=self.getter(obj)returnstorage[key]def__set__(self,obj,value):ifself.read_only:raiseAttributeError("Read-Only property.")getattr(obj,self.attr)[self.key]=valuedef__delete__(self,obj):ifself.read_only:raiseAttributeError("Read-Only property.")delgetattr(obj,self.attr)[self.key]classRequest:def__init__(self,environ=None):self.environ{}ifenviron isNoneelseenvrionself.envrion['bottle.request']=self@DictProperty('environ','bottle.request.query',read_only=True)defquery(self):get=self.environ['bottle.get']=FormsDict()pairs=_parse_qsl(self.environ.get('QUERY_STRING',''))forkey,value inpairs:get[key]=valuereturnget

query方法的邏輯和程式碼都比較簡單,就是從environ中獲取’QUERY_STRING’,並用把原始查詢字串解析為一個FormsDict,將這個FormsDict賦值給environ[‘bottle.request.query’]並返回。但這個函式的裝飾器的作用就有些難以理解,裝飾器的實現方式都是”dunder”特殊方法,有些晦澀難懂。如果上來就看這些原始碼可能難以理解程式碼實現的功能。那不如這些放一邊,假設自己要實現這些方法,你會寫出什麼程式碼。

一開始你可能寫出這樣的程式碼。

Python
1234567891011 # version 1classRequest:"""    some codes here    """defquery(self):get=self.environ['bottle.get']=FormsDict()pairs=_parse_qsl(self.environ.get('QUERY_STRING',''))forkey,value inpairs:get[key]=valuereturnget

這樣確實實現瞭解析查詢字串的功能,但每次在呼叫這個方法時都需要對原始查詢字串解析一次,實際上在處理某特請求時,查詢字串是不會改變的,所以我們只需要解析一次並把它儲存起來,下次使用時直接返回就好了。另外此時的query方法還是一個普通方法,必須使用這樣的方法來呼叫它

Python
1234 # 獲取idrequest.query().id# 獲取pagerequest.query().page

query後面的小括號讓語句顯得不那麼協調,其實就是我覺得它醜。要是也能和官方文件中的示例實現以屬性訪問的方式獲取相應的資料就好了。所以程式碼還得改改。

Python
12345678910111213 # query method version 2classRequest:"""    some codes here    """@propertydefquery(self):if'bootle.get.query'notinself.environ:get=self.environ['bottle.get']=FormsDict()pairs=_parse_qsl(self.environ.get('QUERY_STRING',''))forkey,value inpairs:get[key]=valuereturnself.environ['bottle.get.query']

第二版改變的程式碼就兩處,一個是使用property裝飾器,實現了request.query的訪問方式;另一個就是在query函式體中增加了判斷’bottle.get.query’是否在environ中的判斷語句,實現了只解析一次的要求。第二版幾乎滿足了所有要求,它表現得就像Bottle中真正的query方法一樣。但它還是有些缺陷。

首先,Request類並不只有query一個方法,如果要編寫完整的Request類就會發現,有很多方法的程式碼與query相似,都是從environ中解析出需要的資料,而且都只需要解析一次,儲存起來,第二次或以後訪問時返回儲存的資料就好了。所以可以考慮將屬性管理的程式碼從方法體內抽象出來,正好Python中的描述符可以實現這樣的功能。另外如果使用Bottle的開發者在寫程式碼時不小心嘗試進行request.query = some_data的賦值時,將會丟擲如下錯誤。

Python
1 >>>AttributeError:can'tsetattribute

我們確實希望屬性是隻讀的,在對其賦值時應該丟擲錯誤,但這樣的報錯資訊並沒有提供太多有用的資訊,導致調bug時一頭霧水,找不到方向。我們更希望丟擲如

Python
1 >>>AttributeError:Read-only property

這樣明確的錯誤資訊。

所以第三版的程式碼可以這樣寫

Python
12345678910111213141516171819202122232425262728293031323334353637 # query method version 3classDescriptor:def__init__(self,attr,key,getter,read_only=False):self.attr=attrself.key=keyself.getter=getterself.read_only=read_onlydef__set__(self,obj,value):ifself.read_only:raiseAttributeError('Read only property.')getattr(obj,self.attr)[self.key]=valuedef__get__(self,obj,cls):ifobj isNone:returnselfkey,storage=self.key,getattr(obj,self.attr)ifkey notinstorage:storage[key]=self.getter(obj)returnstorage[key]def__delete__(self,obj):ifself.read_only:raiseAttributeError('Read only property.')delgetattr(obj,self.attr)[self.key]classReqeust:"""    some codes    """defquery(self):get=self.environ['bottle.get']=FormsDict()pairs=_parse_qsl(self.environ.get('QUERY_STRING',''))forkey,value inpairs:get[key]=valuereturnget  query=Descriptor('environ','bottle.get.query',query,read_only=True)

第三版的程式碼沒有使用property裝飾器,而是使用了描述符這個技巧。如果你之前沒有見到過描述符,在這裡限於篇幅只能做個簡單的介紹,但描述符涉及知識點眾多,如果有不清楚之處可以看看《流暢的Python》第20章屬性描述符,裡面有非常詳細的介紹。

簡單來說,描述符是對多個屬性運用相同存取邏輯的一種方式,如Bottle框架裡我們需要對很多屬性都進行判斷某個鍵是否在environ中,如果在則返回,如果不在,需要解析一次這樣的存取邏輯。而描述符需要實現特定協議,包括__set__, __get__, __delete___方法,分別對應設定,讀取和刪除屬性的方法。他麼的引數也比較特殊,如__get__方法的三個引數self, obj, cls分別對應描述符例項的引用,對第三版的程式碼來說就是Descriptor(‘environ’, ‘bottle.get.query’, query, read_only=True)建立的例項的引用;obj則對應將某個屬性託管給描述的例項物件的引用,對應的應該為request物件;而cls則為Request類的引用。在呼叫request.query時編譯器會自動傳入這些引數。如果以Request.query的方式呼叫,那麼obj引數的傳入值為None,這時候通常的處理是返回描述符例項。

在Descriptor中__get__方法的程式碼最多,也比較難理解,但如果記住其引數的意義也沒那麼難。下面以query的實現為例,我新增一些註釋來幫助理解

相關推薦

Bottle 框架裝飾描述應用

最近在閱讀Python微型Web框架Bottle的原始碼,發現了Bottle中有一個既是裝飾器類又是描述符的有趣實現。剛好這兩個點是Python比較的難理解,又混合在一起,讓程式碼有些晦澀難懂。但理解程式碼之後不由得為Python語言的簡潔優美讚歎。所以把相關知識和想法稍微整理

OC學習篇之---Foundation框架的NSArrayNSMutableArray

在之前的一篇文章中介紹了Foundation框架中的NSString類和NSMutableString類:今天我們繼續來看一下Foundation框架中的NSArray類和NSMutableArray類,其實NSArray類和Java中的List差不多,算是一種資料結構,當然

python裝飾的使用裝飾方法的使用

前面一遍講述了裝飾器的基本知識,正好最近有個系統需要進行許可權控制,那麼我們就例項分析下裝飾器的使用。裝飾器是一個面向切面程式設計,主要作用就是許可權控制,插入日誌,效能測試,事務處理,快取等。對於重要的系統我們僅僅控制登入是不夠的,對於固定人員使用到的系統我們還是要進行許

Python快取裝飾適用於redis,memcacheddict物件等

簡介 在web開發中,快取是經常用來提高伺服器的響應速度以及減少資料庫壓力的用力手段。在處理快取時,有三個重要的步驟生成快取鍵,存入快取和獲取快取資料。對於不同的快取軟體(Redis,Memcached等)操作基本相同,只是在具體的儲存獲取環節存在差異,所以將常用的快取處理過程封裝成一個

---裝飾的使用(實驗樓學習筆記)

你可能想要更精確的調整控制屬性訪問許可權,你可以使用 @property 裝飾器,@property 裝飾器就是負責把一個方法變成屬性呼叫的。 例如: #!/usr/bin/env python3 class Account(object):  

C#使用匿名初始化

程式碼如下: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespa

contextmanager: 上下文管理上下文管理裝飾

原文 上下文管理器是在Python2.5之後加入的功能,可以在方便的需要的時候比較精確地分配和釋放資源, with便是上下文管理器的最廣泛的應用, 比如: with open("test/test.txt","w

python-裝飾,與對象,私有字段,析構,__call__,繼承,多繼承,接口

裝飾器 類與對象 私有字段 析構 __call__ 繼承 1、裝飾器執行流程裝飾器:將原函數替換為wrapper函數def outer()@outer --- func1作為參數傳入outer()def wrapper() --- wrapper()放入內存return wrapp

python裝飾你真的理解嗎?

步驟 strong class ron 執行 裝飾器 code ... .... def w1(func): print(‘裝飾器1....‘) def w1_in(): print(‘w1_in.....‘) func()

對Python裝飾(Decorator)的理解與進階

python decorator 裝飾器 有時候我們項目中的某些功能做些修改即需要對內部的某些函數添加一些附加功能,但是為了安全起見不想改變函數的源代碼以及函數的調用方式,那麽裝飾器在這個地方會給我們帶來很大的幫助。 裝飾器(Decorator):(又叫語法糖) 定義:本質是函數,功能(裝

Python裝飾的用法

pla 復制 bsp 函數名 知識點 高階函數 opened play func 定義: 裝飾器本身就是一個函數 為其他函數提供附加功能 不改變源代碼 不改變原調用方式 裝飾器=高階函數+嵌套函數 知識點: 函數本身就是一個變量(意味著可以被復制給一個變量:test=

8.Python裝飾是什麽?

調用 自己 bsp cnblogs http 透明 函數的調用方式 源代碼 ecif Python中裝飾器是什麽? A Python decorator is a specific change that we make in Python syntax to alter

Django框架的視圖模板

select標簽 self 不為 shee 渲染 上傳文件 當前 www current 視圖views django中的視圖就是用來定義函數來處理一些邏輯的核心地方。 django中通過urls來建立路徑跟views中的視圖函數的映射關系。 urls中的映射關系 ‘‘

深入解讀php抽象(abstract)抽象方法

抽象類 php 抽象方法 在面向對象(OOP)語言中,一個類可以有一個或多個子類,而每個類都有至少一個公有方法作為外部代碼訪問的接口。而抽象方法就是為了方便繼承而引入的,現在來看一下抽象類和抽象方法分別是如何定義以及他們的特點。什麽是抽象方法?我們在類裏面定義的只有方法名沒有方法體的方法就是抽象方

c#常用集合集合接口之集合系列【轉】

arr 關聯 special rect 替代 不能 一個數 lock resize 常用集合接口系列:http://www.cnblogs.com/fengxiaojiu/p/7997704.html 常用集合類系列:http://www.cnblogs.com/fengx

flask框架-decorator裝飾

裝飾器 並且 aps 進行 __name__ pos too body rgs 調用包: from functools import wraps 裝飾器其實就是一個函數:參數是一個函數,返回值是一個函數 1.裝飾器使用是通過@符號,在函數的上面 2.裝飾器中定義的函數,要使

知識點 - python 裝飾@staticmethod@classmethod區別使用

定義 整潔 參數 sel spa elf pri Go assm 1.通常來說,我們使用一個類的方法時,首先要實例化這個類,再用實例化的類來調用其方法 class Test(object): """docstring for Test""" def

解析ABP框架的事務處理工作單元,ABP事務處理

回滾 rtu 新的 ola 方式 repo amp assigned 對象 通用連接和事務管理方法連接和事務管理是使用數據庫的應用程序最重要的概念之一。當你開啟一個數據庫連接,什麽時候開始事務,如何釋放連接...諸如此類的。 正如大家都知道的,.Net使用連接池(conne

Scrapy(爬蟲框架),Spiderparse()方法的工作機制

生成 工作 就會 ffffff 遞歸 賦值 () 其他 根據 parse(self,response):當請求url返回網頁沒有指定回調函數,默認的Request對象的回調函數,用來處理網頁返回的response,和生成的Item或者Request對象 以下分析一下pars

Python編程系列---Python裝飾的幾種形式及萬能裝飾

-s ont font 分析 spa def 結果 形式 one 根據函數是否傳參 是否有返回值 ,可以分析出裝飾器的四種形式: 形式一:無參無返回值 1 def outer(func): 2 def wrapper(): 3 print(