flask 源码解析:上下文
这是 flask 源码解析系列文章的其中一篇,本系列所有文章列表:
上下文(application context 和 request context)上下文一直是计算机中难理解的概念,在知乎的一个问题下面有个很通俗易懂的回答:
比如,在 flask 中,视图函数需要知道它执行情况的请求信息(请求的 url,参数,方法等)以及应用信息(应用中初始化的数据库等),才能够正确运行。 最直观地做法是把这些信息封装成一个对象,作为参数传递给视图函数。但是这样的话,所有的视图函数都需要添加对应的参数,即使该函数内部并没有使用到它。 flask 的做法是把这些信息作为类似全局变量的东西,视图函数需要的时候,可以使用? 那么如何实现这种效果呢?如果对 python 多线程比较熟悉的话,应该知道多线程中有个非常类似的概念? flask 中有两种上下文: def?_lookup_req_object(name): ????top?=?_request_ctx_stack.top????if?top?is?None: ????????raise?RuntimeError(_request_ctx_err_msg) ????return?getattr(top,?name)def?_lookup_app_object(name): ????top?=?_app_ctx_stack.top????if?top?is?None: ????????raise?RuntimeError(_app_ctx_err_msg) ????return?getattr(top,?name)def?_find_app(): ????top?=?_app_ctx_stack.top????if?top?is?None: ????????raise?RuntimeError(_app_ctx_err_msg) ????return?top.app#?context?locals_request_ctx_stack?=?LocalStack()_app_ctx_stack?=?LocalStack()current_app?=?LocalProxy(_find_app)request?=?LocalProxy(partial(_lookup_req_object,?'request'))session?=?LocalProxy(partial(_lookup_req_object,?'session'))g?=?LocalProxy(partial(_lookup_app_object,?'g'))
这里的实现用到了两个东西: #?since?each?thread?has?its?own?greenlet?we?can?just?use?those?as?identifiers#?for?the?context.??If?greenlets?are?not?available?we?fall?back?to?the#?current?thread?ident?depending?on?where?it?is.try: ????from?greenlet?import?getcurrent?as?get_identexcept?ImportError: ????try: ????????from?thread?import?get_ident????except?ImportError: ????????from?_thread?import?get_identclass?Local(object): ????__slots__?=?('__storage__',?'__ident_func__') ????def?__init__(self): ????????#?数据保存在?__storage__?中,后续访问都是对该属性的操作 ????????object.__setattr__(self,?'__storage__',?{}) ????????object.__setattr__(self,?'__ident_func__',?get_ident) ????def?__call__(self,?proxy): ????????"""Create?a?proxy?for?a?name.""" ????????return?LocalProxy(self,?proxy) ????#?清空当前线程/协程保存的所有数据 ????def?__release_local__(self): ????????self.__storage__.pop(self.__ident_func__(),?None) ????#?下面三个方法实现了属性的访问、设置和删除。 ????#?注意到,内部都调用?`self.__ident_func__`?获取当前线程或者协程的?id,然后再访问对应的内部字典。 ????#?如果访问或者删除的属性不存在,会抛出?AttributeError。 ????#?这样,外部用户看到的就是它在访问实例的属性,完全不知道字典或者多线程/协程切换的实现 ????def?__getattr__(self,?name): ????????try: ????????????return?self.__storage__[self.__ident_func__()][name] ????????except?KeyError: ????????????raise?AttributeError(name) ????def?__setattr__(self,?name,?value): ????????ident?=?self.__ident_func__() ????????storage?=?self.__storage__????????try: ????????????storage[ident][name]?=?value????????except?KeyError: ????????????storage[ident]?=?{name:?value} ????def?__delattr__(self,?name): ????????try: ????????????del?self.__storage__[self.__ident_func__()][name] ????????except?KeyError: ????????????raise?AttributeError(name) 可以看到, 除了这些基本操作之外, 理解了? __release_local__?可以用来清空当前线程或者协程的栈数据, class?LocalStack(object): ????"""This?class?works?similar?to?a?:class:`Local`?but?keeps?a?stack ????of?objects?instead.?""" ????def?__init__(self): ????????self._local?=?Local() ????def?__release_local__(self): ????????self._local.__release_local__() ????def?__call__(self): ????????def?_lookup(): ????????????rv?=?self.top????????????if?rv?is?None: ????????????????raise?RuntimeError('object?unbound') ????????????return?rv????????return?LocalProxy(_lookup) ????#?push、pop?和?top?三个方法实现了栈的操作, ????#?可以看到栈的数据是保存在?self._local.stack?属性中的 ????def?push(self,?obj): ????????"""Pushes?a?new?item?to?the?stack""" ????????rv?=?getattr(self._local,?'stack',?None) ????????if?rv?is?None: ????????????self._local.stack?=?rv?=?[] ????????rv.append(obj) ????????return?rv????def?pop(self): ????????"""Removes?the?topmost?item?from?the?stack,?will?return?the ????????old?value?or?`None`?if?the?stack?was?already?empty. ????????""" ????????stack?=?getattr(self._local,?None) ????????if?stack?is?None: ????????????return?None ????????elif?len(stack)?==?1: ????????????release_local(self._local) ????????????return?stack[-1] ????????else: ????????????return?stack.pop()????@property ????def?top(self): ????????"""The?topmost?item?on?the?stack.??If?the?stack?is?empty,????????`None`?is?returned. ????????""" ????????try: ????????????return?self._local.stack[-1] ????????except?(AttributeError,?IndexError): ????????????return?None 我们在之前看到了? _request_ctx_stack?=?LocalStack() 它会当前线程或者协程的请求都保存在栈里,等使用的时候再从里面读取。至于为什么要用到栈结构,而不是直接使用? class?LocalProxy(object): ????"""Acts?as?a?proxy?for?a?werkzeug?local. ????Forwards?all?operations?to?a?proxied?object.?""" ????__slots__?=?('__local',?'__dict__',?'__name__') ????def?__init__(self,?local,?name=None): ????????object.__setattr__(self,?'_LocalProxy__local',?local) ????????object.__setattr__(self,?'__name__',?name) ????def?_get_current_object(self): ????????"""Return?the?current?object.""" ????????if?not?hasattr(self.__local,?'__release_local__'): ????????????return?self.__local() ????????try: ????????????return?getattr(self.__local,?self.__name__) ????????except?AttributeError: ????????????raise?RuntimeError('no?object?bound?to?%s'?%?self.__name__)????@property ????def?__dict__(self): ????????try: ????????????return?self._get_current_object().__dict__????????except?RuntimeError: ????????????raise?AttributeError('__dict__') ????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 这里实现的关键是把通过参数传递进来的? NOTE:前面双下划线的属性,会保存到? 然后? 继续回到? _request_ctx_stack?=?LocalStack()request?=?LocalProxy(partial(_lookup_req_object,?'session')) 再次看这段代码希望能看明白, 那么请求上下文信息是什么被放在 stack 中呢?还记得之前介绍的? ctx?=?self.request_context(environ)ctx.push() 每次在调用? 我们来看看 def?request_context(self,?environ): ????return?RequestContext(self,?environ) 它调用了? class?RequestContext(object): ????"""The?request?context?contains?all?request?relevant?information.??It?is ????created?at?the?beginning?of?the?request?and?pushed?to?the ????`_request_ctx_stack`?and?removed?at?the?end?of?it.??It?will?create?the ????URL?adapter?and?request?object?for?the?WSGI?environment?provided. ????""" ????def?__init__(self,?app,?environ,?request=None): ????????self.app?=?app????????if?request?is?None: ????????????request?=?app.request_class(environ) ????????self.request?=?request ????????self.url_adapter?=?app.create_url_adapter(self.request) ????????self.match_request() ????def?match_request(self): ????????"""Can?be?overridden?by?a?subclass?to?hook?into?the?matching ????????of?the?request. ????????""" ????????try: ????????????url_rule,?self.request.view_args?=? ????????????????self.url_adapter.match(return_rule=True) ????????????self.request.url_rule?=?url_rule????????except?HTTPException?as?e: ????????????self.request.routing_exception?=?e????def?push(self): ????????"""Binds?the?request?context?to?the?current?context.""" ????????#?Before?we?push?the?request?context?we?have?to?ensure?that?there ????????#?is?an?application?context. ????????app_ctx?=?_app_ctx_stack.top????????if?app_ctx?is?None?or?app_ctx.app?!=?self.app: ????????????app_ctx?=?self.app.app_context() ????????????app_ctx.push() ????????????self._implicit_app_ctx_stack.append(app_ctx) ????????else: ????????????self._implicit_app_ctx_stack.append(None) ????????_request_ctx_stack.push(self) ????????self.session?=?self.app.open_session(self.request) ????????if?self.session?is?None: ????????????self.session?=?self.app.make_null_session() ????def?pop(self,?exc=_sentinel): ????????"""Pops?the?request?context?and?unbinds?it?by?doing?that.??This?will ????????also?trigger?the?execution?of?functions?registered?by?the ????????:meth:`~flask.Flask.teardown_request`?decorator. ????????""" ????????app_ctx?=?self._implicit_app_ctx_stack.pop() ????????try: ????????????clear_request?=?False ????????????if?not?self._implicit_app_ctx_stack: ????????????????self.app.do_teardown_request(exc) ????????????????request_close?=?getattr(self.request,?'close',?None) ????????????????if?request_close?is?not?None: ????????????????????request_close() ????????????????clear_request?=?True ????????finally: ????????????rv?=?_request_ctx_stack.pop() ????????????#?get?rid?of?circular?dependencies?at?the?end?of?the?request ????????????#?so?that?we?don't?require?the?GC?to?be?active. ????????????if?clear_request: ????????????????rv.request.environ['werkzeug.request']?=?None ????????????#?Get?rid?of?the?app?as?well?if?necessary. ????????????if?app_ctx?is?not?None: ????????????????app_ctx.pop(exc) ????def?auto_pop(self,?exc): ????????if?self.request.environ.get('flask._preserve_context')?or????????????(exc?is?not?None?and?self.app.preserve_context_on_exception): ????????????self.preserved?=?True ????????????self._preserved_exc?=?exc????????else: ????????????self.pop(exc) ????def?__enter__(self): ????????self.push() ????????return?self????def?__exit__(self,?exc_type,?exc_value,?tb): ????????self.auto_pop(exc_value) 每个 request context 都保存了当前请求的信息,比如 request 对象和 app 对象。在初始化的最后,还调用了? 到这里,上下文的实现就比较清晰了:每次有请求过来的时候,flask 会先创建当前线程或者进程需要处理的两个重要上下文对象,把它们保存到隔离的栈里面,这样视图函数进行处理的时候就能直接从栈上获取这些信息。 NOTE:因为 app 实例只有一个,因此多个? 到这里,关于 context 的实现和功能已经讲解得差不多了。还有两个疑惑没有解答。
第一个答案是“灵活度”,第二个答案是“多 application”。虽然在实际运行中,每个请求对应一个 request context 和一个 application context,但是在测试或者 python shell 中运行的时候,用户可以单独创建 request context 或者 application context,这种灵活度方便用户的不同的使用场景;而且栈可以让 redirect 更容易实现,一个处理函数可以从栈中获取重定向路径的多个请求信息。application 设计成栈也是类似,测试的时候可以添加多个上下文,另外一个原因是 flask 可以多个 application 同时运行: from?werkzeug.wsgi?import?DispatcherMiddlewarefrom?frontend_app?import?application?as?frontendfrom?backend_app?import?application?as?backend application?=?DispatcherMiddleware(frontend,?{ ????'/backend':?????backend}) 这个例子就是使用? Update: 为什么要用 LocalProxy写完这篇文章之后,收到有位读者的疑问:为什么要使用? 这是个很好的问题,上面也确实没有很明确地给出这个答案。这里解释一下! 首先明确一点, 我们拿? 如果直接使用? from?flask?import?current_app app?=?create_app()admin_app?=?create_admin_app()def?do_something(): ????with?app.app_context(): ????????work_on(current_app) ????????with?admin_app.app_context(): ????????????work_on(current_app) 这里我们出现了嵌套的 app,每个 with 上下文都需要操作其对应的? 对于? 其实还有一个更大的问题,这个例子也可以看出来。比如我们知道? 所以为什么需要? 参考资料
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |