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

詳細(xì)解讀Python的web.py框架下的application.py模塊

 更新時(shí)間:2015年05月02日 16:02:38   作者:diabloneo  
這篇文章主要介紹了Python的web.py框架下的application.py模塊,作者深入分析了web.py的源碼,需要的朋友可以參考下

本文主要分析的是web.py庫的application.py這個(gè)模塊中的代碼。總的來說,這個(gè)模塊主要實(shí)現(xiàn)了WSGI兼容的接口,以便應(yīng)用程序能夠被WSGI應(yīng)用服務(wù)器調(diào)用。WSGI是Web Server Gateway Interface的縮寫,具體細(xì)節(jié)可以查看WSGI的WIKI頁面
接口的使用
使用web.py自帶的HTTP Server

下面這個(gè)例子來自官方文檔的Hello World,這個(gè)代碼一般是應(yīng)用入口的代碼:

import web

urls = ("/.*", "hello")
app = web.application(urls, globals())

class hello:
  def GET(self):
    return 'Hello, world!'

if __name__ == "__main__":
  app.run()

上面的例子描述了一個(gè)web.py應(yīng)用最基本的組成元素:

  •     URL路由表
  •     一個(gè)web.application實(shí)例app
  •     調(diào)用app.run()

其中,app.run()的調(diào)用是初始化各種WCGI接口,并啟動(dòng)一個(gè)內(nèi)置的HTTP服務(wù)器和這些接口對接,代碼如下:

def run(self, *middleware):
  return wsgi.runwsgi(self.wsgifunc(*middleware))

與WSGI應(yīng)用服務(wù)器對接

如果你的應(yīng)用要與WSGI應(yīng)用服務(wù)器對接,比如uWSGI,gunicorn等,那么應(yīng)用入口的代碼就要換一種寫法了:

import web

class hello:
  def GET(self):
    return 'Hello, world!'

urls = ("/.*", "hello")
app = web.application(urls, globals())
application = app.wsgifunc()

在這種場景下,應(yīng)用的代碼不需要啟動(dòng)HTTP服務(wù)器,而是實(shí)現(xiàn)一個(gè)WSGI兼容的接口供WSGI服務(wù)器調(diào)用。web.py框架為我們實(shí)現(xiàn)了這樣的接口,你只需要調(diào)用application = app.wsgifunc()就可以了,這里所得到的application變量就是WSGI接口(后面分析完代碼你就會(huì)知道了)。
WSGI接口的實(shí)現(xiàn)分析

分析主要圍繞著下面兩行代碼進(jìn)行:

app = web.application(urls, globals())
application = app.wsgifunc()

web.application實(shí)例化

初始化這個(gè)實(shí)例需要傳遞兩個(gè)參數(shù):URL路由元組和globals()的結(jié)果。

另外,還可以傳遞第三個(gè)變量:autoreload,用來指定是否需要自動(dòng)重新導(dǎo)入Python模塊,這在調(diào)試的時(shí)候很有用,不過我們分析主要過程的時(shí)候可以忽略。

application類的初始化代碼如下:

class application:
  def __init__(self, mapping=(), fvars={}, autoreload=None):
    if autoreload is None:
      autoreload = web.config.get('debug', False)
    self.init_mapping(mapping)
    self.fvars = fvars
    self.processors = []

    self.add_processor(loadhook(self._load))
    self.add_processor(unloadhook(self._unload))

    if autoreload:
      ...

其中,autoreload相關(guān)功能的代碼略去了。其他的代碼主要作了如下幾個(gè)事情:

  •     self.init_mapping(mapping):初始化URL路由映射關(guān)系。
  •     self.add_processor():添加了兩個(gè)處理器。

初始化URL路由映射關(guān)系

def init_mapping(self, mapping):
  self.mapping = list(utils.group(mapping, 2))

這個(gè)函數(shù)還調(diào)用了一個(gè)工具函數(shù),效果是這樣的:

urls = ("/", "Index",
    "/hello/(.*)", "Hello",
    "/world", "World")

如果用戶初始化時(shí)傳遞的元組是這樣的,那么調(diào)用init_mapping之后:

self.mapping = [["/", "Index"],
        ["/hello/(.*)", "Hello"],
        ["/world", "World"]]

后面框架在進(jìn)行URL路由時(shí),就會(huì)遍歷這個(gè)列表。
添加處理器

  self.add_processor(loadhook(self._load))
  self.add_processor(unloadhook(self._unload))

這兩行代碼添加了兩個(gè)處理器:self._load和self._unload,而且還對這兩個(gè)函數(shù)進(jìn)行了裝飾。處理器的是用在HTTP請求處理前后的,它不是真正用來處理一個(gè)HTTP請求,但是可以用來作一些額外的工作,比如官方教程里面有提到的給子應(yīng)用添加session的做法,就是使用了處理器:

def session_hook():
  web.ctx.session = session

app.add_processor(web.loadhook(session_hook))

處理器的定義和使用都是比較復(fù)雜的,后面專門講。
wsgifunc函數(shù)

wsgifunc的執(zhí)行結(jié)果是返回一個(gè)WSGI兼容的函數(shù),并且該函數(shù)內(nèi)部實(shí)現(xiàn)了URL路由等功能。

def wsgifunc(self, *middleware):
  """Returns a WSGI-compatible function for this application."""
  ...
  for m in middleware: 
    wsgi = m(wsgi)

  return wsgi

除開內(nèi)部函數(shù)的定義,wsgifunc的定義就是這么簡單,如果沒有實(shí)現(xiàn)任何中間件,那么就是直接返回其內(nèi)部定義的wsgi函數(shù)。
wsgi函數(shù)

該函數(shù)實(shí)現(xiàn)了WSGI兼容接口,同時(shí)也實(shí)現(xiàn)了URL路由等功能。

def wsgi(env, start_resp):
  # clear threadlocal to avoid inteference of previous requests
  self._cleanup()

  self.load(env)
  try:
    # allow uppercase methods only
    if web.ctx.method.upper() != web.ctx.method:
      raise web.nomethod()

    result = self.handle_with_processors()
    if is_generator(result):
      result = peep(result)
    else:
      result = [result]
  except web.HTTPError, e:
    result = [e.data]

  result = web.safestr(iter(result))

  status, headers = web.ctx.status, web.ctx.headers
  start_resp(status, headers)

  def cleanup():
    self._cleanup()
    yield '' # force this function to be a generator

  return itertools.chain(result, cleanup())

for m in middleware: 
  wsgi = m(wsgi)

return wsgi

下面來仔細(xì)分析一下這個(gè)函數(shù):

  self._cleanup()
  self.load(env)

self._cleanup()內(nèi)部調(diào)用utils.ThreadedDict.clear_all(),清除所有的thread local數(shù)據(jù),避免內(nèi)存泄露(因?yàn)閣eb.py框架的很多數(shù)據(jù)都會(huì)保存在thread local變量中)。

self.load(env)使用env中的參數(shù)初始化web.ctx變量,這些變量涵蓋了當(dāng)前請求的信息,我們在應(yīng)用中有可能會(huì)使用到,比如web.ctx.fullpath。

  try:
    # allow uppercase methods only
    if web.ctx.method.upper() != web.ctx.method:
      raise web.nomethod()

    result = self.handle_with_processors()
    if is_generator(result):
      result = peep(result)
    else:
      result = [result]
  except web.HTTPError, e:
    result = [e.data]

這一段主要是調(diào)用self.handle_with_processors(),這個(gè)函數(shù)會(huì)對請求的URL進(jìn)行路由,找到合適的類或子應(yīng)用來處理該請求,也會(huì)調(diào)用添加的處理器來做一些其他工作(關(guān)于處理器的部分,后面專門講)。對于處理的返回結(jié)果,可能有三種方式:

  1.     返回一個(gè)可迭代對象,則進(jìn)行安全迭代處理。
  2.     返回其他值,則創(chuàng)建一個(gè)列表對象來存放。
  3.     如果拋出了一個(gè)HTTPError異常(比如我們使用raise web.OK("hello, world")這種方式來返回結(jié)果時(shí)),則將異常中的數(shù)據(jù)e.data封裝成一個(gè)列表。

-

  result = web.safestr(iter(result))

  status, headers = web.ctx.status, web.ctx.headers
  start_resp(status, headers)

  def cleanup():
    self._cleanup()
    yield '' # force this function to be a generator

  return itertools.chain(result, cleanup())

接下來的這段代碼,會(huì)對前面返回的列表result進(jìn)行字符串化處理,得到HTTP Response的body部分。然后根據(jù)WSGI的規(guī)范作如下兩個(gè)事情:

  1.     調(diào)用start_resp函數(shù)。
  2.     將result結(jié)果轉(zhuǎn)換成一個(gè)迭代器。

現(xiàn)在你可以看到,之前我們提到的application = app.wsgifunc()就是將wsgi函數(shù)賦值給application變量,這樣應(yīng)用服務(wù)器就可以采用WSGI標(biāo)準(zhǔn)和我們的應(yīng)用對接了。
處理HTTP請求

前面分析的代碼已經(jīng)說明了web.py框架如何實(shí)現(xiàn)WSGI兼容接口的,即我們已經(jīng)知道了HTTP請求到達(dá)框架以及從框架返回給應(yīng)用服務(wù)器的流程。那么框架內(nèi)部是如何調(diào)用我們的應(yīng)用代碼來實(shí)現(xiàn)一個(gè)請求的處理的呢?這個(gè)就需要詳細(xì)分析剛才忽略掉的處理器的添加和調(diào)用過程。
loadhook和unloadhook裝飾器

這兩個(gè)函數(shù)是真實(shí)處理器的函數(shù)的裝飾器函數(shù)(雖然他的使用不是采用裝飾器的@操作符),裝飾后得到的處理器分別對應(yīng)請求處理之前(loadhook)和請求處理之后(unloadhook)。
loadhook

def loadhook(h):
  def processor(handler):
    h()
    return handler()

  return processor

這個(gè)函數(shù)返回一個(gè)函數(shù)processor,它會(huì)確保先調(diào)用你提供的處理器函數(shù)h,然后再調(diào)用后續(xù)的操作函數(shù)handler。
unloadhook

def unloadhook(h):
  def processor(handler):
    try:
      result = handler()
      is_generator = result and hasattr(result, 'next')
    except:
      # run the hook even when handler raises some exception
      h()
      raise

    if is_generator:
      return wrap(result)
    else:
      h()
      return result

  def wrap(result):
    def next():
      try:
        return result.next()
      except:
        # call the hook at the and of iterator
        h()
        raise

    result = iter(result)
    while True:
      yield next()

  return processor

這個(gè)函數(shù)也返回一個(gè)processor,它會(huì)先調(diào)用參數(shù)傳遞進(jìn)來的handler,然后再調(diào)用你提供的處理器函數(shù)。
handle_with_processors函數(shù)

def handle_with_processors(self):
  def process(processors):
    try:
      if processors:
        p, processors = processors[0], processors[1:]
        return p(lambda: process(processors))
      else:
        return self.handle()
    except web.HTTPError:
      raise
    except (KeyboardInterrupt, SystemExit):
      raise
    except:
      print >> web.debug, traceback.format_exc()
      raise self.internalerror()

  # processors must be applied in the resvere order. (??)
  return process(self.processors)

這個(gè)函數(shù)挺復(fù)雜的,最核心的部分采用了遞歸實(shí)現(xiàn)(我感覺不遞歸應(yīng)該也能實(shí)現(xiàn)同樣的功能)。為了說明清晰,采用實(shí)例說明。

前面有提到,初始化application實(shí)例的時(shí)候,會(huì)添加兩個(gè)處理器到self.processors:

  self.add_processor(loadhook(self._load))
  self.add_processor(unloadhook(self._unload))

所以,現(xiàn)在的self.processors是下面這個(gè)樣子的:

self.processors = [loadhook(self._load), unloadhook(self._unload)]

# 為了方便后續(xù)說明,我們縮寫一下:

self.processors = [load_processor, unload_processor]

當(dāng)框架開始執(zhí)行handle_with_processors的時(shí)候,是逐個(gè)執(zhí)行這些處理器的。我們還是來看代碼分解,首先簡化一下handle_with_processors函數(shù):

    def handle_with_processors(self):
      def process(processors):
        try:
          if processors: # 位置2
            p, processors = processors[0], processors[1:]
            return p(lambda: process(processors)) # 位置3
          else:
            return self.handle() # 位置4
        except web.HTTPError:
          raise
        ...
    
      # processors must be applied in the resvere order. (??)
      return process(self.processors) # 位置1
    
    
  •     函數(shù)執(zhí)行的起點(diǎn)是位置1,調(diào)用其內(nèi)部定義函數(shù)process(processors)。
  •     如果位置2判斷處理器列表不為空,則進(jìn)入if內(nèi)部。
  •     在位置3調(diào)用本次需要執(zhí)行的處理器函數(shù),參數(shù)為一個(gè)lambda函數(shù),然后返回。
  •     如果位置2判斷處理器列表為空,則執(zhí)行self.handle(),該函數(shù)真正的調(diào)用我們的應(yīng)用代碼(下面會(huì)講到)。

以上面的例子來說,目前有兩個(gè)處理器:

self.processors = [load_processor, unload_processor]

從位置1進(jìn)入代碼后,在位置2會(huì)判斷還有處理器要執(zhí)行,會(huì)走到位置3,此時(shí)要執(zhí)行代碼是這樣的:

return load_processor(lambda: process([unload_processor]))

load_processor函數(shù)是一個(gè)經(jīng)過loadhook裝飾的函數(shù),因此其定義在執(zhí)行時(shí)是這樣的:

def load_processor(lambda: process([unload_processor])):
  self._load()
  return process([unload_processor]) # 就是參數(shù)的lambda函數(shù)

會(huì)先執(zhí)行self._load(),然后再繼續(xù)執(zhí)行process函數(shù),依舊會(huì)走到位置3,此時(shí)要執(zhí)行的代碼是這樣的:

return unload_processor(lambda: process([]))

unload_processor函數(shù)是一個(gè)經(jīng)過unloadhook裝飾的函數(shù),因此其定義在執(zhí)行時(shí)是這樣的:

def unload_processor(lambda: process([])):
  try:
    result = process([]) # 參數(shù)傳遞進(jìn)來的lambda函數(shù)
    is_generator = result and hasattr(result, 'next')
  except:
    # run the hook even when handler raises some exception
    self._unload()
    raise

  if is_generator:
    return wrap(result)
  else:
    self._unload()
    return result

現(xiàn)在會(huì)先執(zhí)行process([])函數(shù),并且走到位置4(調(diào)用self.handle()的地方),從而得到應(yīng)用的處理結(jié)果,然后再調(diào)用本處理器的處理函數(shù)self._unload()。

總結(jié)一下執(zhí)行的順序:

  self._load()
    self.handle()
  self._unload()

如果還有更多的處理器,也是按照這種方法執(zhí)行下去,對于loadhook裝飾的處理器,先添加的先執(zhí)行,對于unloadhook裝飾的處理器,后添加的先執(zhí)行。
handle函數(shù)

講了這么多,才講到真正要調(diào)用我們寫的代碼的地方。在所有的load處理器執(zhí)行完之后,就會(huì)執(zhí)行self.handle()函數(shù),其內(nèi)部會(huì)調(diào)用我們寫的應(yīng)用代碼。比如返回個(gè)hello, world之類的。self.handle的定義如下:

def handle(self):
  fn, args = self._match(self.mapping, web.ctx.path)
  return self._delegate(fn, self.fvars, args)

這個(gè)函數(shù)就很好理解了,第一行調(diào)用的self._match是進(jìn)行路由功能,找到對應(yīng)的類或者子應(yīng)用,第二行的self._delegate就是調(diào)用這個(gè)類或者傳遞請求到子應(yīng)用。
_match函數(shù)

_match函數(shù)的定義如下:

def _match(self, mapping, value):
  for pat, what in mapping:
    if isinstance(what, application): # 位置1
      if value.startswith(pat):
        f = lambda: self._delegate_sub_application(pat, what)
        return f, None
      else:
        continue
    elif isinstance(what, basestring): # 位置2
      what, result = utils.re_subm('^' + pat + '$', what, value)
    else: # 位置3
      result = utils.re_compile('^' + pat + '$').match(value)

    if result: # it's a match
      return what, [x for x in result.groups()]
  return None, None

該函數(shù)的參數(shù)中mapping就是self.mapping,是URL路由映射表;value則是web.ctx.path,是本次請求路徑。該函數(shù)遍歷self.mapping,根據(jù)映射關(guān)系中處理對象的類型來處理:

  1.     位置1,處理對象是一個(gè)application實(shí)例,也就是一個(gè)子應(yīng)用,則返回一個(gè)匿名函數(shù),該匿名函數(shù)會(huì)調(diào)用self._delegate_sub_application進(jìn)行處理。
  2.     位置2,如果處理對象是一個(gè)字符串,則調(diào)用utils.re_subm進(jìn)行處理,這里會(huì)把value(也就是web.ctx.path)中的和pat匹配的部分替換成what(也就是我們指定的一個(gè)URL模式的處理對象字符串),然后返回替換后的結(jié)果以及匹配的項(xiàng)(是一個(gè)re.MatchObject實(shí)例)。
  3.     位置3,如果是其他情況,比如直接指定一個(gè)類對象作為處理對象。

如果result非空,則返回處理對象和一個(gè)參數(shù)列表(這個(gè)參數(shù)列表就是傳遞給我們實(shí)現(xiàn)的GET等函數(shù)的參數(shù))。
_delegate函數(shù)

從_match函數(shù)返回的結(jié)果會(huì)作為參數(shù)傳遞給_delegate函數(shù):

fn, args = self._match(self.mapping, web.ctx.path)
return self._delegate(fn, self.fvars, args)

其中:

  •     fn:是要處理當(dāng)前請求的對象,一般是一個(gè)類名。
  •     args:是要傳遞給請求處理對象的參數(shù)。
  •     self.fvars:是實(shí)例化application時(shí)的全局名稱空間,會(huì)用于查找處理對象。

_delegate函數(shù)的實(shí)現(xiàn)如下:

def _delegate(self, f, fvars, args=[]):
  def handle_class(cls):
    meth = web.ctx.method
    if meth == 'HEAD' and not hasattr(cls, meth):
      meth = 'GET'
    if not hasattr(cls, meth):
      raise web.nomethod(cls)
    tocall = getattr(cls(), meth)
    return tocall(*args)

  def is_class(o): return isinstance(o, (types.ClassType, type))

  if f is None:
    raise web.notfound()
  elif isinstance(f, application):
    return f.handle_with_processors()
  elif is_class(f):
    return handle_class(f)
  elif isinstance(f, basestring):
    if f.startswith('redirect '):
      url = f.split(' ', 1)[1]
      if web.ctx.method == "GET":
        x = web.ctx.env.get('QUERY_STRING', '')
        if x:
          url += '?' + x
      raise web.redirect(url)
    elif '.' in f:
      mod, cls = f.rsplit('.', 1)
      mod = __import__(mod, None, None, [''])
      cls = getattr(mod, cls)
    else:
      cls = fvars[f]
    return handle_class(cls)
  elif hasattr(f, '__call__'):
    return f()
  else:
    return web.notfound()

這個(gè)函數(shù)主要是根據(jù)參數(shù)f的類型來做出不同的處理:

  •     f為空,則返回302 Not Found.
  •     f是一個(gè)application實(shí)例,則調(diào)用子應(yīng)用的handle_with_processors()進(jìn)行處理。
  •     f是一個(gè)類對象,則調(diào)用內(nèi)部函數(shù)handle_class。
  •     f是一個(gè)字符串,則進(jìn)行重定向處理,或者獲取要處理請求的類名后,調(diào)用handle_class進(jìn)行處理(我們寫的代碼一般是在這個(gè)分支下被調(diào)用的)。
  •     f是一個(gè)可調(diào)用對象,直接調(diào)用。
  •     其他情況返回302 Not Found.

 

相關(guān)文章

  • Python操作json的方法實(shí)例分析

    Python操作json的方法實(shí)例分析

    這篇文章主要介紹了Python操作json的方法,結(jié)合實(shí)例形式簡單分析了Python針對json數(shù)據(jù)使用解碼loads()和編碼dumps()相關(guān)操作技巧,需要的朋友可以參考下
    2018-12-12
  • Django權(quán)限控制的使用

    Django權(quán)限控制的使用

    這篇文章主要介紹了Django權(quán)限控制的使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • Python中的生成器

    Python中的生成器

    這篇文章主要介紹了Python中的生成器
    2021-12-12
  • python遍歷小寫英文字母的方法

    python遍歷小寫英文字母的方法

    今天小編就為大家分享一篇python遍歷小寫英文字母的方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-01-01
  • 教你使用python實(shí)現(xiàn)微信每天給女朋友說晚安

    教你使用python實(shí)現(xiàn)微信每天給女朋友說晚安

    非常棒的一個(gè)python小實(shí)戰(zhàn),文章主要教大家如何用python實(shí)現(xiàn)微信每天給女朋友說晚安,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-03-03
  • 淺談python socket函數(shù)中,send與sendall的區(qū)別與使用方法

    淺談python socket函數(shù)中,send與sendall的區(qū)別與使用方法

    下面小編就為大家?guī)硪黄獪\談python socket函數(shù)中,send與sendall的區(qū)別與使用方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-05-05
  • 如何利用飾器實(shí)現(xiàn) Python 函數(shù)重載

    如何利用飾器實(shí)現(xiàn) Python 函數(shù)重載

    這篇文章主要介紹了如何利用飾器實(shí)現(xiàn) Python 函數(shù)重載,需要的朋友可以參考下面文章內(nèi)容,希望能幫助到你
    2021-09-09
  • Python深入學(xué)習(xí)之特殊方法與多范式

    Python深入學(xué)習(xí)之特殊方法與多范式

    這篇文章主要介紹了Python深入學(xué)習(xí)之特殊方法與多范式,特殊方法就像PHP里的魔術(shù)方法,多范式是指可以用面向?qū)ο?、面向過程、函數(shù)式、聲明式等方式編寫程序,需要的朋友可以參考下
    2014-08-08
  • Anaconda如何查看自己目前安裝的包詳解

    Anaconda如何查看自己目前安裝的包詳解

    Anaconda是一種用于數(shù)據(jù)科學(xué)和機(jī)器學(xué)習(xí)的開源發(fā)行版,它包含了很多常用的Python包和工具,如NumPy、Pandas、Scipy、Scikit-Learn等,下面這篇文章主要給大家介紹了關(guān)于Anaconda如何查看自己目前安裝的包的相關(guān)資料,需要的朋友可以參考下
    2023-05-05
  • python中子類與父類的關(guān)系基礎(chǔ)知識(shí)點(diǎn)

    python中子類與父類的關(guān)系基礎(chǔ)知識(shí)點(diǎn)

    在本篇文章里小編給大家整理的是一篇關(guān)于python中子類與父類的關(guān)系基礎(chǔ)知識(shí)點(diǎn)內(nèi)容,對此有興趣的朋友們可以學(xué)習(xí)下。
    2021-02-02

最新評論