欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Bottle框架中的裝飾器類和描述符應(yīng)用詳解

 更新時(shí)間:2017年10月28日 14:48:47   作者:Rookie  
這篇文章主要介紹了Bottle框架中的裝飾器類和描述符應(yīng)用詳解,具有一定參考價(jià)值,需要的朋友可以了解下。

最近在閱讀Python微型Web框架Bottle的源碼,發(fā)現(xiàn)了Bottle中有一個(gè)既是裝飾器類又是描述符的有趣實(shí)現(xiàn)。剛好這兩個(gè)點(diǎn)是Python比較的難理解,又混合在一起,讓代碼有些晦澀難懂。但理解代碼之后不由得為Python語言的簡潔優(yōu)美贊嘆。所以把相關(guān)知識(shí)和想法稍微整理,以供分享。

正文

Bottle是Python的一個(gè)微型Web框架,所有代碼都在一個(gè)bottle.py文件中,只依賴標(biāo)準(zhǔn)庫實(shí)現(xiàn),兼容Python 2和Python 3,而且最新的穩(wěn)定版0.12代碼也只有3700行左右。雖然小,但它實(shí)現(xiàn)了Web框架基本功能。這里就不以過多的筆墨去展示Bottle框架,需要的請(qǐng)?jiān)L問其網(wǎng)站了解更多。這里著重介紹與本文相關(guān)的重要對(duì)象request。在Bottle里,request對(duì)象代表了當(dāng)前線程處理的請(qǐng)求,客戶端發(fā)送的請(qǐng)求數(shù)據(jù)如表單數(shù)據(jù),請(qǐng)求網(wǎng)站和cookie都可以從request對(duì)象中獲得。下面是官方文檔中的兩個(gè)例子
from bottle import request, route, response, template

# 獲取客戶端cookie以實(shí)現(xiàn)登陸時(shí)問候用戶功能
@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是如何實(shí)現(xiàn)的呢?根據(jù)WSGI接口規(guī)定,所有的HTTP請(qǐng)求信息都包含在一個(gè)名為envrion的dict對(duì)象中。所以Bottle要做的就是把HTTP請(qǐng)求信息從environ解析出來。在深入Request類如何實(shí)現(xiàn)之前先要了解下Bottle的FormsDict。FormsDict與字典類相似,但擴(kuò)展了一些功能,比如支持屬性訪問、一對(duì)多的鍵值對(duì)、WTForms支持等。它在Bottle中被廣泛應(yīng)用,如上面的示例中cookie和query數(shù)據(jù)都以FormsDict存儲(chǔ),所以我們可以用request.query.page的方式獲取相應(yīng)屬性值。

下面是0.12版Bottle中Request類的部分代碼,0.12版中Request類繼承了BaseRequest,為了方便閱讀我把代碼合并在一起,同時(shí)還有重要的DictProperty的代碼。需要說明的是Request類__init__傳入的environ參數(shù)就是WSGI協(xié)議中包含HTTP請(qǐng)求信息的envrion,而query方法中的_parse_qsl函數(shù)可以接受形如/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

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

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

# version 1
class Request:
  """
  some codes here
  """
  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

這樣確實(shí)實(shí)現(xiàn)了解析查詢字符串的功能,但每次在調(diào)用這個(gè)方法時(shí)都需要對(duì)原始查詢字符串解析一次,實(shí)際上在處理某特請(qǐng)求時(shí),查詢字符串是不會(huì)改變的,所以我們只需要解析一次并把它保存起來,下次使用時(shí)直接返回就好了。另外此時(shí)的query方法還是一個(gè)普通方法,必須使用這樣的方法來調(diào)用它

# 獲取id
request.query().id
# 獲取page
request.query().page

query后面的小括號(hào)讓語句顯得不那么協(xié)調(diào),其實(shí)就是我覺得它丑。要是也能和官方文檔中的示例實(shí)現(xiàn)以屬性訪問的方式獲取相應(yīng)的數(shù)據(jù)就好了。所以代碼還得改改。

# query method version 2
class Request:
  """
  some codes here
  """
  @property
  def query(self):
    if 'bootle.get.query' not in self.environ:
      get = self.environ['bottle.get'] = FormsDict()
      pairs = _parse_qsl(self.environ.get('QUERY_STRING', ''))
      for key, value in pairs:
        get[key] = value
    return self.environ['bottle.get.query']

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

首先,Request類并不只有query一個(gè)方法,如果要編寫完整的Request類就會(huì)發(fā)現(xiàn),有很多方法的代碼與query相似,都是從environ中解析出需要的數(shù)據(jù),而且都只需要解析一次,保存起來,第二次或以后訪問時(shí)返回保存的數(shù)據(jù)就好了。所以可以考慮將屬性管理的代碼從方法體內(nèi)抽象出來,正好Python中的描述符可以實(shí)現(xiàn)這樣的功能。另外如果使用Bottle的開發(fā)者在寫代碼時(shí)不小心嘗試進(jìn)行request.query = some_data的賦值時(shí),將會(huì)拋出如下錯(cuò)誤。

>>> AttributeError: can't set attribute

我們確實(shí)希望屬性是只讀的,在對(duì)其賦值時(shí)應(yīng)該拋出錯(cuò)誤,但這樣的報(bào)錯(cuò)信息并沒有提供太多有用的信息,導(dǎo)致調(diào)bug時(shí)一頭霧水,找不到方向。我們更希望拋出如

>>> AttributeError: Read-only property

這樣明確的錯(cuò)誤信息。

所以第三版的代碼可以這樣寫

# query method version 3
class Descriptor:
  def __init__(self, attr, key, getter, read_only=False):
    self.attr = attr
    self.key = key
    self.getter = getter
    self.read_only = read_only
  def __set__(self, obj, value):
    if self.read_only:
        raise AttributeError('Read only property.')
    getattr(obj, self.attr)[self.key] = value
  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 __delete__(self, obj):
    if self.read_only:
      raise AttributeError('Read only property.')
    del getattr(obj, self.attr)[self.key]
class Reqeust:
  """
  some codes
  """
  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 
  query = Descriptor('environ', 'bottle.get.query', query, read_only=True)

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

簡單來說,描述符是對(duì)多個(gè)屬性運(yùn)用相同存取邏輯的一種方式,如Bottle框架里我們需要對(duì)很多屬性都進(jìn)行判斷某個(gè)鍵是否在environ中,如果在則返回,如果不在,需要解析一次這樣的存取邏輯。而描述符需要實(shí)現(xiàn)特定協(xié)議,包括__set__, __get__, __delete___方法,分別對(duì)應(yīng)設(shè)置,讀取和刪除屬性的方法。他么的參數(shù)也比較特殊,如__get__方法的三個(gè)參數(shù)self, obj, cls分別對(duì)應(yīng)描述符實(shí)例的引用,對(duì)第三版的代碼來說就是Descriptor(‘environ', ‘bottle.get.query', query, read_only=True)創(chuàng)建的實(shí)例的引用;obj則對(duì)應(yīng)將某個(gè)屬性托管給描述的實(shí)例對(duì)象的引用,對(duì)應(yīng)的應(yīng)該為request對(duì)象;而cls則為Request類的引用。在調(diào)用request.query時(shí)編譯器會(huì)自動(dòng)傳入這些參數(shù)。如果以Request.query的方式調(diào)用,那么obj參數(shù)的傳入值為None,這時(shí)候通常的處理是返回描述符實(shí)例。

在Descriptor中__get__方法的代碼最多,也比較難理解,但如果記住其參數(shù)的意義也沒那么難。下面以query的實(shí)現(xiàn)為例,我添加一些注釋來幫助理解

key, storage = self.key, getattr(obj, self.attr)
# key='bottle.get.query'
# storage = environ 即包含HTTP請(qǐng)求的信息的environ
 
# 判斷envrion中是否包含key來決定是否需要解析
if key not in storage:
  storage[key] = self.getter(obj)
  # self.getter(obj)就是調(diào)用了原來的query方法,不過要傳入一個(gè)Request實(shí)例,也就是obj
return storage[key]

而__set__, __delete__代碼比較簡單,在這里我們把只讀屬性在賦值和刪除時(shí)拋出的錯(cuò)誤定制為AttributeError(‘Read only property.'),方便調(diào)試。

通過使用描述符這個(gè)有些難懂的方法,我們可以在Request的方法中專心于編寫如何解析的代碼,不用擔(dān)心屬性的存取邏輯。和在每個(gè)方法中都使用if判斷相比高到不知道哪里去。但美中不足的是,這樣讓我們的方法代碼后面拖著一個(gè)“小尾巴”,即

query = Descriptor('envrion', 'bottle.get.query', query, read_only=True)

怎么去掉這個(gè)這個(gè)“小尾巴“呢?回顧之前的代碼幾乎都是對(duì)query之類的方法進(jìn)行修飾,所以可以嘗試使用裝飾器,畢竟裝飾器就是對(duì)某個(gè)函數(shù)進(jìn)行修飾的,而且我們應(yīng)該使用參數(shù)化的裝飾器,這樣才能將envrion等參數(shù)傳遞給裝飾器。如果要實(shí)現(xiàn)參數(shù)化裝飾器就需要一個(gè)裝飾器工廠函數(shù),也就是說裝飾器的代碼里需要嵌套至少3個(gè)函數(shù)體,寫起來有寫繞,代碼可閱讀性也有差。更大的問題來自如何將描述符與裝飾器結(jié)合起來,因?yàn)镈escriptor是一個(gè)類而不是方法。

解決辦法其實(shí)挺簡單的。如果知道Python中函數(shù)也是對(duì)象,實(shí)現(xiàn)了__call__方法的對(duì)象可以表現(xiàn)得像函數(shù)一樣。所以我們可以修改Descirptor的代碼,實(shí)現(xiàn)__call__方法,讓它的實(shí)例成為callable對(duì)象就可以把它用作裝飾器;而要傳入的參數(shù)可以以實(shí)例屬性存儲(chǔ)起來,通過self.attribute的形式訪問,而不是像使用工廠函數(shù)實(shí)現(xiàn)參數(shù)化裝飾器時(shí)通過閉包來實(shí)現(xiàn)參數(shù)的訪問獲取。這時(shí)候再來看看Bottle里的DictProperty代碼

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]

其實(shí)就是一個(gè)有描述符作用的裝飾器類,它的使用方法很簡單:

@DictProperty('environ', 'bottle.get.query', read_only=True)
def query(self):
  """ some codes """

拆開會(huì)更好理解點(diǎn):

property = DictProperty('environ', 'bottle.get.query', read_only=True)
@property
def query(self):
  """ some codes """

再把@實(shí)現(xiàn)的語法糖拆開:

def query(self):
  """ some codes """
 
property = DictProperty('environ', 'bottle.get.query', read_only=True)
query = property(query) # @實(shí)現(xiàn)的語法糖

再修改以下代碼形式:

def query(self):
  """ some codes """
 
query = DictProperty('environ', 'bottle.get.query', read_only=True)(query)

是不是和第三版的實(shí)現(xiàn)方式非常相似。

def query(self):
  """ some codes """
 
query = Descriptor('environ', 'bottle.get.query', query, read_only=True)

但我們可以使用裝飾器把方法體后面那個(gè)不和諧的賦值語句”小尾巴“去掉,將屬性存取管理抽象出來,而且只需要使用一行非常簡便的裝飾器把這個(gè)功能添加到某個(gè)方法上。這也許就是Python的美之一吧。

寫在后面

DictProperty涉及知識(shí)遠(yuǎn)不止文中涉及的那么簡單,如果你還是不清楚DictProperty的實(shí)現(xiàn)功能,建議閱讀《流暢的Python》第7章和第22章,對(duì)裝飾器和描述符有詳細(xì)的描述,另外《Python Cookbook》第三版第9章元編程有關(guān)于參數(shù)化裝飾器和裝飾器類的敘述和示例。如果你對(duì)Bottle為什么要實(shí)現(xiàn)這樣的功能感到困惑,建議閱讀Bottle的文檔和WSGI相關(guān)的文章。

其實(shí)前一陣再閱讀Bottle源碼時(shí)就想寫一篇文章,但奈何許久不寫東西文筆生疏加上醫(yī)院實(shí)習(xí)期間又比較忙,一直推到現(xiàn)在才終于磕磕絆絆地把我閱讀的Bottle源碼的一些感悟?qū)懗鰜?,希望?duì)喜歡Python的各位有些幫助把。

總結(jié)

以上就是本文關(guān)于Bottle框架中的裝飾器類和描述符應(yīng)用詳解的全部內(nèi)容,希望對(duì)大家有所幫助。感謝朋友們對(duì)本站的支持!

相關(guān)文章

  • 利用Python?爬取股票實(shí)時(shí)數(shù)據(jù)詳情

    利用Python?爬取股票實(shí)時(shí)數(shù)據(jù)詳情

    這篇文章主要介紹了利用Python?爬取股票實(shí)時(shí)數(shù)據(jù)詳情,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下
    2022-08-08
  • Python作用域與名字空間原理詳解

    Python作用域與名字空間原理詳解

    這篇文章主要介紹了python作用域與名字空間原理詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-03-03
  • 如何在python字符串中輸入純粹的{}

    如何在python字符串中輸入純粹的{}

    這篇文章主要介紹了如何在python字符串中輸入純粹的{}以及python字符串連接的三種方法,需要的朋友可以參考下
    2018-08-08
  • python執(zhí)行l(wèi)inux系統(tǒng)命令的三種方式小結(jié)

    python執(zhí)行l(wèi)inux系統(tǒng)命令的三種方式小結(jié)

    本文介紹三種在python執(zhí)行l(wèi)inux命令的方式,三種方式都是基于python的標(biāo)準(zhǔn)庫實(shí)現(xiàn),因此不需要額外安裝第三方庫,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-02-02
  • 使用pyqt5搭建yolo3目標(biāo)識(shí)別界面的方法

    使用pyqt5搭建yolo3目標(biāo)識(shí)別界面的方法

    這篇文章主要介紹了使用pyqt5搭建yolo3目標(biāo)識(shí)別界面的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • 總結(jié)分析python數(shù)據(jù)化運(yùn)營關(guān)聯(lián)規(guī)則

    總結(jié)分析python數(shù)據(jù)化運(yùn)營關(guān)聯(lián)規(guī)則

    本文內(nèi)容主要介紹了python數(shù)據(jù)化運(yùn)營中關(guān)聯(lián)規(guī)則的一般應(yīng)用場(chǎng)景,以及關(guān)聯(lián)規(guī)則的實(shí)現(xiàn),并例舉了適應(yīng)的應(yīng)用示例,方便大家更直觀的理解應(yīng)用
    2021-08-08
  • python使用tkinter調(diào)整label背景顏色的測(cè)試

    python使用tkinter調(diào)整label背景顏色的測(cè)試

    這篇文章主要介紹了python使用tkinter調(diào)整label背景顏色的測(cè)試方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • Django 對(duì)象關(guān)系映射(ORM)源碼詳解

    Django 對(duì)象關(guān)系映射(ORM)源碼詳解

    這篇文章主要介紹了Django 對(duì)象關(guān)系映射(ORM)源碼詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-08-08
  • Python實(shí)現(xiàn)切割mp3片段并降低碼率

    Python實(shí)現(xiàn)切割mp3片段并降低碼率

    MoviePy是一個(gè)基于Python的視頻編輯庫,它提供了創(chuàng)建、編輯、合并、剪輯和轉(zhuǎn)換視頻的功能,所以本文主要介紹如何使用moviepy來分割音頻流并降低碼率,感興趣的可以了解下
    2023-08-08
  • 利用matlab與Excel交互之單元格操作

    利用matlab與Excel交互之單元格操作

    Excel是廣泛使用的“電子表格”,Matlab則具有強(qiáng)大的數(shù)值計(jì)算、統(tǒng)計(jì)分析以及圖形可視化能力,這篇文章主要給大家介紹了關(guān)于利用matlab與Excel交互之單元格操作的相關(guān)資料,需要的朋友可以參考下
    2021-08-08

最新評(píng)論