首页 > 编程语言 >web.py框架下的application.py模块 —— Python

web.py框架下的application.py模块 —— Python

时间:2024-07-04 17:59:07浏览次数:3  
标签:web return processors Python self py result 函数

本文主要分析的是web.py库的application.py这个模块中的代码。总的来说,这个模块主要实现了WSGI兼容的接口,以便应用程序能够被WSGI应用服务器调用。WSGI是Web Server Gateway Interface的缩写,具体细节可以查看WSGI的WIKI页面
接口的使用
使用web.py自带的HTTP Server

下面这个例子来自官方文档的Hello World,这个代码一般是应用入口的代码:

import web



urls = ("/.*", "hello")

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



class hello:

  def GET(self):

    return 'Hello, world!'



if __name__ == "__main__":

  app.run()

上面的例子描述了一个web.py应用最基本的组成元素:

  •     URL路由表
  •     一个web.application实例app
  •     调用app.run()

其中,app.run()的调用是初始化各种WCGI接口,并启动一个内置的HTTP服务器和这些接口对接,代码如下:

def run(self, *middleware):

  return wsgi.runwsgi(self.wsgifunc(*middleware))

与WSGI应用服务器对接

如果你的应用要与WSGI应用服务器对接,比如uWSGI,gunicorn等,那么应用入口的代码就要换一种写法了:

import web



class hello:

  def GET(self):

    return 'Hello, world!'



urls = ("/.*", "hello")

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

application = app.wsgifunc()

在这种场景下,应用的代码不需要启动HTTP服务器,而是实现一个WSGI兼容的接口供WSGI服务器调用。web.py框架为我们实现了这样的接口,你只需要调用application = app.wsgifunc()就可以了,这里所得到的application变量就是WSGI接口(后面分析完代码你就会知道了)。
WSGI接口的实现分析

分析主要围绕着下面两行代码进行:

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

application = app.wsgifunc()

web.application实例化

初始化这个实例需要传递两个参数:URL路由元组和globals()的结果。

另外,还可以传递第三个变量:autoreload,用来指定是否需要自动重新导入Python模块,这在调试的时候很有用,不过我们分析主要过程的时候可以忽略。

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相关功能的代码略去了。其他的代码主要作了如下几个事情:

  •     self.init_mapping(mapping):初始化URL路由映射关系。
  •     self.add_processor():添加了两个处理器。

初始化URL路由映射关系

def init_mapping(self, mapping):

  self.mapping = list(utils.group(mapping, 2))

这个函数还调用了一个工具函数,效果是这样的:

urls = ("/", "Index",

    "/hello/(.*)", "Hello",

    "/world", "World")

如果用户初始化时传递的元组是这样的,那么调用init_mapping之后:

self.mapping = [["/", "Index"],

        ["/hello/(.*)", "Hello"],

        ["/world", "World"]]

后面框架在进行URL路由时,就会遍历这个列表。
添加处理器

self.add_processor(loadhook(self._load))

self.add_processor(unloadhook(self._unload))

这两行代码添加了两个处理器:self._load和self._unload,而且还对这两个函数进行了装饰。处理器的是用在HTTP请求处理前后的,它不是真正用来处理一个HTTP请求,但是可以用来作一些额外的工作,比如官方教程里面有提到的给子应用添加session的做法,就是使用了处理器:

def session_hook():

  web.ctx.session = session



app.add_processor(web.loadhook(session_hook))

处理器的定义和使用都是比较复杂的,后面专门讲。
wsgifunc函数

wsgifunc的执行结果是返回一个WSGI兼容的函数,并且该函数内部实现了URL路由等功能。

def wsgifunc(self, *middleware):

  """Returns a WSGI-compatible function for this application."""

  ...

  for m in middleware:

    wsgi = m(wsgi)



  return wsgi

除开内部函数的定义,wsgifunc的定义就是这么简单,如果没有实现任何中间件,那么就是直接返回其内部定义的wsgi函数。
wsgi函数

该函数实现了WSGI兼容接口,同时也实现了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

下面来仔细分析一下这个函数:

self._cleanup()

self.load(env)

self._cleanup()内部调用utils.ThreadedDict.clear_all(),清除所有的thread local数据,避免内存泄露(因为web.py框架的很多数据都会保存在thread local变量中)。

self.load(env)使用env中的参数初始化web.ctx变量,这些变量涵盖了当前请求的信息,我们在应用中有可能会使用到,比如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]

这一段主要是调用self.handle_with_processors(),这个函数会对请求的URL进行路由,找到合适的类或子应用来处理该请求,也会调用添加的处理器来做一些其他工作(关于处理器的部分,后面专门讲)。对于处理的返回结果,可能有三种方式:

  1.     返回一个可迭代对象,则进行安全迭代处理。
  2.     返回其他值,则创建一个列表对象来存放。
  3.     如果抛出了一个HTTPError异常(比如我们使用raise web.OK("hello, world")这种方式来返回结果时),则将异常中的数据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())

接下来的这段代码,会对前面返回的列表result进行字符串化处理,得到HTTP Response的body部分。然后根据WSGI的规范作如下两个事情:

  1.     调用start_resp函数。
  2.     将result结果转换成一个迭代器。

现在你可以看到,之前我们提到的application = app.wsgifunc()就是将wsgi函数赋值给application变量,这样应用服务器就可以采用WSGI标准和我们的应用对接了。
处理HTTP请求

前面分析的代码已经说明了web.py框架如何实现WSGI兼容接口的,即我们已经知道了HTTP请求到达框架以及从框架返回给应用服务器的流程。那么框架内部是如何调用我们的应用代码来实现一个请求的处理的呢?这个就需要详细分析刚才忽略掉的处理器的添加和调用过程。
loadhook和unloadhook装饰器

这两个函数是真实处理器的函数的装饰器函数(虽然他的使用不是采用装饰器的@操作符),装饰后得到的处理器分别对应请求处理之前(loadhook)和请求处理之后(unloadhook)。
loadhook

def loadhook(h):

  def processor(handler):

    h()

    return handler()



  return processor

这个函数返回一个函数processor,它会确保先调用你提供的处理器函数h,然后再调用后续的操作函数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

这个函数也返回一个processor,它会先调用参数传递进来的handler,然后再调用你提供的处理器函数。
handle_with_processors函数

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)

这个函数挺复杂的,最核心的部分采用了递归实现(我感觉不递归应该也能实现同样的功能)。为了说明清晰,采用实例说明。

前面有提到,初始化application实例的时候,会添加两个处理器到self.processors:

self.add_processor(loadhook(self._load))

self.add_processor(unloadhook(self._unload))

所以,现在的self.processors是下面这个样子的:

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

# 为了方便后续说明,我们缩写一下:

self.processors = [load_processor, unload_processor]

当框架开始执行handle_with_processors的时候,是逐个执行这些处理器的。我们还是来看代码分解,首先简化一下handle_with_processors函数:

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
  •     函数执行的起点是位置1,调用其内部定义函数process(processors)。
  •     如果位置2判断处理器列表不为空,则进入if内部。
  •     在位置3调用本次需要执行的处理器函数,参数为一个lambda函数,然后返回。
  •     如果位置2判断处理器列表为空,则执行self.handle(),该函数真正的调用我们的应用代码(下面会讲到)。

以上面的例子来说,目前有两个处理器:

self.processors = [load_processor, unload_processor]

从位置1进入代码后,在位置2会判断还有处理器要执行,会走到位置3,此时要执行代码是这样的:

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

load_processor函数是一个经过loadhook装饰的函数,因此其定义在执行时是这样的:

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

  self._load()

  return process([unload_processor]) # 就是参数的lambda函数

会先执行self._load(),然后再继续执行process函数,依旧会走到位置3,此时要执行的代码是这样的:

return unload_processor(lambda: process([]))

unload_processor函数是一个经过unloadhook装饰的函数,因此其定义在执行时是这样的:

def unload_processor(lambda: process([])):

  try:

    result = process([]) # 参数传递进来的lambda函数

    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

现在会先执行process([])函数,并且走到位置4(调用self.handle()的地方),从而得到应用的处理结果,然后再调用本处理器的处理函数self._unload()。

总结一下执行的顺序:

self._load()

  self.handle()

self._unload()

如果还有更多的处理器,也是按照这种方法执行下去,对于loadhook装饰的处理器,先添加的先执行,对于unloadhook装饰的处理器,后添加的先执行。
handle函数

讲了这么多,才讲到真正要调用我们写的代码的地方。在所有的load处理器执行完之后,就会执行self.handle()函数,其内部会调用我们写的应用代码。比如返回个hello, world之类的。self.handle的定义如下:

def handle(self):

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

  return self._delegate(fn, self.fvars, args)

这个函数就很好理解了,第一行调用的self._match是进行路由功能,找到对应的类或者子应用,第二行的self._delegate就是调用这个类或者传递请求到子应用。
_match函数

_match函数的定义如下:

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

该函数的参数中mapping就是self.mapping,是URL路由映射表;value则是web.ctx.path,是本次请求路径。该函数遍历self.mapping,根据映射关系中处理对象的类型来处理:

  1.     位置1,处理对象是一个application实例,也就是一个子应用,则返回一个匿名函数,该匿名函数会调用self._delegate_sub_application进行处理。
  2.     位置2,如果处理对象是一个字符串,则调用utils.re_subm进行处理,这里会把value(也就是web.ctx.path)中的和pat匹配的部分替换成what(也就是我们指定的一个URL模式的处理对象字符串),然后返回替换后的结果以及匹配的项(是一个re.MatchObject实例)。
  3.     位置3,如果是其他情况,比如直接指定一个类对象作为处理对象。

如果result非空,则返回处理对象和一个参数列表(这个参数列表就是传递给我们实现的GET等函数的参数)。
_delegate函数

从_match函数返回的结果会作为参数传递给_delegate函数:

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

return self._delegate(fn, self.fvars, args)

其中:

  •     fn:是要处理当前请求的对象,一般是一个类名。
  •     args:是要传递给请求处理对象的参数。
  •     self.fvars:是实例化application时的全局名称空间,会用于查找处理对象。

_delegate函数的实现如下:

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()

这个函数主要是根据参数f的类型来做出不同的处理:

    •     f为空,则返回302 Not Found.
    •     f是一个application实例,则调用子应用的handle_with_processors()进行处理。
    •     f是一个类对象,则调用内部函数handle_class。
    •     f是一个字符串,则进行重定向处理,或者获取要处理请求的类名后,调用handle_class进行处理(我们写的代码一般是在这个分支下被调用的)。
    •     f是一个可调用对象,直接调用。
    •     其他情况返回302 Not Found.

本次分享到此结束,感谢大家的阅读! 

标签:web,return,processors,Python,self,py,result,函数
From: https://blog.csdn.net/xuezhe5212/article/details/140155569

相关文章

  • python爬虫1-requests库
    requests库requests提供发送网络请求和处理响应的方法安装pipinstallrequestsGET请求importrequestsurl='https://www.baidu.com/'#urlparams={'key1':'value1','key2':'value2'}#参数#发送get请求......
  • python爬虫2-HTML文本处理
    HTML文本处理re模式匹配正则表达式是一种强大的字符串匹配和处理工具,允许通过指定的模式来查找、替换和验证字符串。函数编译正则表达式re.compile(pattern,flags=0):将字符串形式的正则表达式编译为一个正则对象,用于后续的匹配操作。匹配操作re.match(pattern,str......
  • python基础操作
    pip常用命令列出已安装的所有库:piplist显示包信息:pipshow库名下载库:pipdownload库名python库的安装方式1、pipinstall库名-ihttps://mirrors.aliyun.com/pypi/simple(国内镜像库速度更快)方式2、pipinstallwhl文件路径方式3、解压后的安装包路径下,pythonsetup.pyinstal......
  • torch.tensor、numpy.array、list三者之间互相转换
    torch.tensor、numpy.array、list三者之间互相转换1.1list转numpyndarray=np.array(list)1.2numpy转listlist=ndarray.tolist()2.1list转torch.Tensortensor=torch.Tensor(list)2.2torch.Tensor转list先转numpy,后转listlist=tensor.numpy().tolist(......
  • 使用python基本库代码实现神经网络常见层
    一:批量归一化(BatchNormalization)代码解释:函数定义:batch_norm函数接受输入数据X、缩放参数gamma、平移参数beta和一个小常数epsilon,用于防止除零错误。X的形状为(N,D),其中N是批量大小,D是特征维度。gamma和beta的形状为(1,D)。计算批量均值和方差:me......
  • 【web APIs】快速上手Day03(Dom事件进阶)
    目录WebAPIs-第3天全选文本框案例事件流事件捕获事件冒泡阻止冒泡解绑事件on事件方式解绑addEventListener方式解绑注意事项-鼠标经过事件的区别两种注册事件的区别事件委托综合案例-tab栏切换改造其他事件页面加载事件元素滚动事件页面滚动事件-获取位置页面滚动......
  • sublime text3 修改 exec.py文件编译警告返回信息,去掉绝对路径
    第一步:找到exec.py文件1.找到路径:C:\SublimeText3\Packages。2.找到Default.sublime-package复制一个备份,后缀改成Default.rar并且解压缩,在解压缩文件里面找到exec.py文件。3.复制exec.py文件到 C:\SublimeText3\Data\Packages\User下面,或者从编辑器上面打开......
  • 【python学习笔记】Python装饰器
    装饰器参考:搞懂Python装饰器Python@wraps修饰器装饰器是什么有兴趣的可以参考PEP318的原文DecoratorsforFunctionsandMethods解释了语法用途以及设计出来装饰器的动机Thecurrentmethodfortransformingfunctionsandmethods(forinstance,declaringthem......
  • python教程:自定义函数
    1.多态我们可以看到,Python不用考虑输入的数据类型,而是将其交给具体的代码去判断执行,同样的一个函数(比如这边的相加函数my_sum()),可以同时应用在整型、列表、字符串等等的操作中。在编程语言中,我们把这种行为称为多态。这也是Python和其他语言,比如Java、C等很大的一个不同点......
  • Python教程:空值、无穷值判断之isna、isnull、isfinite
    一、空值isnaPands中NaN(Not-A-Number)视为空值,利用函数isna和notna进行判断。注意:不要利用是否等于None判断是否为空!importpandasaspdpd.NA==None#Falsepd.isna(pd.NA)#Truepd.isna(None)#Truepd.notna(pd.NA)#Falsepd.notna(None)#False二、......