2023年11月29日发(作者:)
Flask进阶(⼀)——请求上下⽂和应⽤上下⽂完全解答
(上)
前⾔:
flask的轻便和强⼤的扩展性能会让web的初级开发者甚⾄是有经验的开发者神往。
flask能在短时间内快速搭建web的后台,⽽《flask web开发--基于python的web应⽤开发实战》是最好的flask⼊门教程了。
但当中对应⽤上下⽂和请求上下⽂的讲解有点简单,本⽂对这两个重要的概念做⼀个总结,⽅便⾃⼰以后的回顾。
由于不看源码是很难理解上下⽂的前世今⽣,所有本⽂在某些地⽅会涉及到flask的源码,由于flask和werkzug结合得很紧密,werkzeug也会有所
展⽰。
预备知识:
1、Thread Local(本地线程)
从⾯向对象设计的⾓度看,对象是保存“状态”的地⽅。Python 也是如此,⼀个对象的状态都被保存在对象携带的⼀个特殊字典中。Thread Local
则是⼀种特殊的对象,它的“状态”对线程隔离 —— 也就是说每个线程对⼀个 Thread Local 对象的修改都不会影响其他线程。
()
import threading
mydata = ()
= 42
为什么werkzeug还⾃⼰搞了⼀套⽽不直接使⽤呢?
在python中,除了线程之外,还有个叫协程的东东,(这⾥不提进程)。java中貌似是⽆法实现协程的。⽽python的协程感觉⾼⼤尚的样
⼦,python3.5开始对协程内置⽀持,⽽且也有相关开源库greenlet等。
协程是什么?
举个例⼦,⽐如⼀个线程在处理IO时,该线程是处于空闲状态的,等待IO返回。但是此时如果不让我们的线程⼲等着cpu时间⽚耗光,有没有其他办
法,解决思路就是采⽤协程处理任务,⼀个线程中可以运⾏多个协程,当当前协程去处理IO时,线程可以马上调度其他协程继续运⾏,⽽不是⼲等着
不⼲活。
flask从客户端获取到请求时,要让视图函数能访问⼀些对象,这样才能处理请求。例如请求对象就是⼀个很好的例⼦。要让视图函数访问请求对
象,⼀个显⽽易见的⽅法就是将其作为参数传⼊视图函数,不过这回导致程序中的每个视图函数都增加⼀个参数,为了避免⼤量可有可⽆才参数把视
图函数弄得⼀团糟,flask使⽤上下⽂临时把某些对象变为全局可访问(只是当前线程的全局可访问)。
正⽂:
1、请求上下⽂(request、session)
(1)⽣命周期
先回顾⼀个请求上下⽂的使⽤例⼦
@('/')
def index():
user_agent = ('User-Agent')
return' Your broswer is %s ' % user_agent
在视图函数中,可以直接使⽤request来获取请求对象。这⾥牵涉到请求上下⽂的⽣命周期问题了。
def handle_request():
print 'handle request'
handle_request() #会收到运⾏时错误:RuntimeError: working outside of request context
request对象只有在请求上下⽂的⽣命周期内才可以访问。离开了请求的⽣命周期,其上下⽂环境也就不存在了,⾃然也⽆法获取request对象。
那什么是请求上下⽂的⽣命周期呢?⼀般就是在视图函数⾥,或者请求钩⼦中,才能使⽤请求上下⽂request。flask在接收到来⾃客户端的请求时,
会帮我们构造请求上下⽂的使⽤环境,当flask根据url跳到相应的视图函数的时候,⾃然⽽然就可以直接使⽤请求上下⽂,请求钩⼦也是同理。
此外,在不同http请求之间得到的request必然也是不⼀样的,它只包含当前http请求的⼀些信息,是线程隔离的。
⽽session不⼀样,session是可以跨请求的,在不同的http请求之间,session都是同⼀个,因此,可以借session来存储⼀些请求之间需要‘记
住’的字典值。
(2)请求上下⽂环境构造(本篇幅有点长,涉及flask在接收http请求后的⼯作过程)
from flask import Flask
app = Flask(__name__) #⽣成app实例
@('/')
def index():
return 'Hello World'
flask是遵循WSGI接⼝的web框架,因此它会实现⼀个类似如下形式的函数以供服务器调⽤:
def application(environ, start_response): #⼀个符合wsgi协议的应⽤程序写法应该接受2个参数
start_response('200 OK', [('Content-Type', 'text/html')]) #environ为http的相关信息,如请求头等 start_response则是响应信息
return [b'Hello, web!
'] #return出来是响应内容
class Flask(_PackageBoundObject): #Flask类
#中间省略⼀些代码
def __call__(self, environ, start_response): #Flask实例的__call__⽅法
"""Shortcut for :attr:`wsgi_app`."""
return _app(environ, start_response) #注意他的return,他返回的时候,实际上是调⽤了wsgi_app这个功能
如此⼀来,我们便知道,当http请求从server发送过来的时候,他会启动__call__功能,最终实际是调⽤了wsgi_app功能并传⼊environ和
start_response。
接下来查看flask中符合wsgi接⼝的这么⼀个函数wsgi_app:
class Flask(_PackageBoundObject):
#中间省略⼀些代码
def wsgi_app(self, environ, start_response):
ctx = t_context(environ)
()
error = None
try:
try:
response = _dispatch_request() #full_dispatch_request起到了预处理和错误处理以及分发请求的作⽤
except Exception as e:
error = e
response = _response(_exception(e)) #如果有错误发⽣,则⽣成错误响应
class RequestContext(object):
def __init__(self, app, environ, request=None):
= app #app是Flask(__name__)实例
if request is None:
request = t_class(environ) #request_class实质上是rs下的class Request。这⾥根据environ创建⼀个Request实例
t = request
_adapter = _url_adapter(t)
s = None
n = None
self._implicit_app_ctx_stack = []
ved = False
self._preserved_exc = None
self._after_request_functions = []
_request()
当第⼀个http请求过来时,request默认是空的,通过t_class(environ)构造
因此ctx获得⼀个刚初始化过的RequestContext实例,ctx包含了当前请求的request、session等各种信息,⾮常重要。
然后使⽤()⽅法,这个⽅法很重要,继续查看:
class RequestContext(object):
def push(self):
top = _request_ctx_ #top其实是个RequestContext实例
if top is not None and ved:
(top._preserved_exc)
app_ctx = _app_ctx_
if app_ctx is None or app_ != :
app_ctx = _context()
app_()
self._implicit_app_ctx_(app_ctx)
else:
self._implicit_app_ctx_(None)
class LocalStack(object):
def __init__(self):
self._local = Local() #由此可以看出LocalStack实例包裹了⼀个Local类的实例
def push(self, obj):
rv = getattr(self._local, 'stack', None)
if rv is None:
self._ = rv = []
(obj)
return rv
def pop(self):
stack = getattr(self._local, 'stack', None)
if stack is None:
return None
elif len(stack) == 1:
release_local(self._local)
return stack[-1]
else:
return ()
@property
def top(self):
try:
return self._[-1]
except (AttributeError, IndexError):
return None
#LocalStack的push、pop、top的操作对象都是⾃⾝的_local对象。
def _lookup_req_object(name):
top = _request_ctx_
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)
# context locals
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
request和session都是先从_request_ctx_stack⾥获取头元素,这个元素是RequestContext实例,它包含了request、session等等的很多信
息。request和session就是从它⾝上获取的。这⾥可以看到,request和session其实是⼀个LocalProxy实例,LocalProxy其实就是⼀个代理,⼀
个为werkzeug的Local对象服务的代理。他把所以作⽤到⾃⼰的操作全部“转发”到 它所代理的对象上去。它初始化的⽅法如下:
@implements_bool
class LocalProxy(object):
__slots__ = ('__local', '__dict__', '__name__', '__wrapped__')
def __init__(self, local, name=None):
# 这⾥有⼀个点需要注意⼀下,通过了__setattr__⽅法,self的
@implements_bool
class LocalProxy(object):
def _get_current_object(self):
"""
获取当前被代理的真正对象,⼀般情况下不会主动调⽤这个⽅法,除⾮你因为
某些性能原因需要获取做这个被代理的真正对象,或者你需要把它⽤来另外的
地⽅。
"""
# 这⾥主要是判断代理的对象是不是⼀个werkzeug的Local对象,在我们分析request
# 的过程中,不会⽤到这块逻辑。
if not hasattr(self.__local, '__release_local__'):
# 从LocalProxy(partial(_lookup_req_object, 'request'))看来
# 通过调⽤self.__local()⽅法,我们得到了 partial(_lookup_req_object, 'request')
# 也就是 ``_request_ctx_t``
return self.__local()
try:
return getattr(self.__local, self.__name__)
except AttributeError:
raise RuntimeError('no object bound to %s' % self.__name__)
# 接下来就是⼀⼤段⼀段的Python的魔法⽅法了,Local Proxy重载了(⼏乎)?所有Python
# 内建魔法⽅法,让所有的关于他⾃⼰的operations都指向到了_get_current_object()
# 所返回的对象,也就是真正的被代理对象。
def __getattr__(self, name):
if name == '__members__':
return dir(self._get_current_object())
return getattr(self._get_current_object(), name)
def __setitem__(self, key, value):
self._get_current_object()[key] = value
request和session⼀般的使⽤如:('args_name')、('args_name') 或者session['name']=value。
这⾥分析⼀个简单的例⼦:page = ('page', 1, type=int)
这⾥会先调⽤request的__getattr__⽅法,返回getattr(self._get_current_object(),'args')获取真正的代理对象,即_request_ctx_stack栈顶的
RequestContext元素,再在它⾝上得到request,再获取args。
class Flask(_PackageBoundObject):
#中间省略⼀些代码
def wsgi_app(self, environ, start_response):
ctx = t_context(environ)
()
error = None
try:
try:
from import EnvironBuilder


发布评论