Python Flask 的上下文堆栈的目的是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/20036520/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What is the purpose of Flask's context stacks?
提问by Ben Davis
I've been using the request/application context for some time without fully understanding how it works or why it was designed the way it was. What is the purpose of the "stack" when it comes to the request or application context? Are these two separate stacks, or are they both part of one stack? Is the request context pushed onto a stack, or is it a stack itself? Am I able to push/pop multiple contexts on top of eachother? If so, why would I want to do that?
我已经使用请求/应用程序上下文有一段时间了,但没有完全理解它是如何工作的,或者为什么它是这样设计的。当涉及到请求或应用程序上下文时,“堆栈”的目的是什么?这是两个独立的堆栈,还是它们都属于一个堆栈?请求上下文是推送到堆栈上,还是堆栈本身?我可以在彼此之上推送/弹出多个上下文吗?如果是这样,我为什么要这样做?
Sorry for all the questions, but I'm still confused after reading the documentation for Request Context and Application Context.
对于所有问题,我很抱歉,但在阅读了 Request Context 和 Application Context 的文档后,我仍然感到困惑。
采纳答案by Mark Hildreth
Multiple Apps
多个应用程序
The application context (and its purpose) is indeed confusing until you realize that Flask can have multiple apps. Imagine the situation where you want to have a single WSGI Python interpreter run multiple Flask application. We're not talking Blueprints here, we're talking entirely different Flask applications.
应用程序上下文(及其目的)确实令人困惑,直到您意识到 Flask 可以有多个应用程序。想象一下你想让一个 WSGI Python 解释器运行多个 Flask 应用程序的情况。我们在这里不是在谈论蓝图,而是在谈论完全不同的 Flask 应用程序。
You might set this up similar to the Flask documentation section on "Application Dispatching"example:
您可以将其设置为类似于“应用程序调度”示例中的Flask 文档部分:
from werkzeug.wsgi import DispatcherMiddleware
from frontend_app import application as frontend
from backend_app import application as backend
application = DispatcherMiddleware(frontend, {
'/backend': backend
})
Notice that there are two completely different Flask applications being created "frontend" and "backend". In other words, the Flask(...)application constructor has been called twice, creating two instances of a Flask application.
请注意,“前端”和“后端”创建了两个完全不同的 Flask 应用程序。换句话说,Flask(...)应用程序构造函数被调用了两次,创建了 Flask 应用程序的两个实例。
Contexts
上下文
When you are working with Flask, you often end up using global variables to access various functionality. For example, you probably have code that reads...
当您使用 Flask 时,您通常最终会使用全局变量来访问各种功能。例如,您可能有读取...
from flask import request
Then, during a view, you might use requestto access the information of the current request. Obviously, requestis not a normal global variable; in actuality, it is a context localvalue. In other words, there is some magic behind the scenes that says "when I call request.path, get the pathattribute from the requestobject of the CURRENT request." Two different requests will have a different results for request.path.
然后,在查看期间,您可能会使用request访问当前请求的信息。显然,request不是一个正常的全局变量;实际上,它是一个上下文本地值。换句话说,幕后有一些魔法说“当我调用 时request.path,path从request当前请求的对象中获取属性”。两个不同的请求将产生不同的结果request.path。
In fact, even if you run Flask with multiple threads, Flask is smart enough to keep the request objects isolated. In doing so, it becomes possible for two threads, each handling a different request, to simultaneously call request.pathand get the correct information for their respective requests.
事实上,即使你用多个线程运行 Flask,Flask 也足够聪明,可以保持请求对象的隔离。这样做时,两个线程(每个线程处理不同的请求)可以同时调用request.path并获取其各自请求的正确信息。
Putting it Together
把它放在一起
So we've already seen that Flask can handle multiple applications in the same interpreter, and also that because of the way that Flask allows you to use "context local" globals there must be some mechanism to determine what the "current" requestis (in order to do things such as request.path).
所以我们已经看到 Flask 可以在同一个解释器中处理多个应用程序,而且由于 Flask 允许您使用“上下文本地”全局变量的方式,必须有某种机制来确定“当前”请求是什么(以便做诸如request.path)之类的事情。
Putting these ideas together, it should also make sense that Flask must have some way to determine what the "current" application is!
将这些想法放在一起,Flask 必须有某种方法来确定“当前”应用程序是什么也应该是有意义的!
You probably also have code similar to the following:
您可能还有类似于以下内容的代码:
from flask import url_for
Like our requestexample, the url_forfunction has logic that is dependent on the current environment. In this case, however, it is clear to see that the logic is heavily dependent on which app is considered the "current" app. In the frontend/backend example shown above, both the "frontend" and "backend" apps could have a "/login" route, and so url_for('/login')should return something different depending on if the view is handling the request for the frontend or backend app.
与我们的request示例一样,该url_for函数具有依赖于当前环境的逻辑。然而,在这种情况下,很明显逻辑在很大程度上取决于哪个应用程序被视为“当前”应用程序。在上面显示的前端/后端示例中,“前端”和“后端”应用程序都可以有一个“/登录”路由,因此url_for('/login')应该返回不同的内容,具体取决于视图是在处理前端应用程序还是后端应用程序的请求。
To answer your questions...
要回答您的问题...
What is the purpose of the "stack" when it comes to the request or application context?
当涉及到请求或应用程序上下文时,“堆栈”的目的是什么?
From the Request Context docs:
从请求上下文文档:
Because the request context is internally maintained as a stack you can push and pop multiple times. This is very handy to implement things like internal redirects.
由于请求上下文在内部维护为堆栈,因此您可以多次推送和弹出。这对于实现内部重定向之类的东西非常方便。
In other words, even though you typically will have 0 or 1 items on these stack of "current" requests or "current" applications, it is possible that you could have more.
换句话说,即使您通常在这些“当前”请求或“当前”应用程序堆栈上有 0 或 1 个项目,但您可能有更多。
The example given is where you would have your request return the results of an "internal redirect". Let's say a user requests A, but you want to return to the user B. In most cases, you issue a redirect to the user, and point the user to resource B, meaning the user will run a second request to fetch B. A slightly different way of handling this would be to do an internal redirect, which means that while processing A, Flask will make a new request to itself for resource B, and use the results of this second request as the results of the user's original request.
给出的示例是您的请求将返回“内部重定向”的结果。假设用户请求 A,但您想返回给用户 B。在大多数情况下,您向用户发出重定向,并将用户指向资源 B,这意味着用户将运行第二个请求来获取 B。A稍微不同的处理方式是进行内部重定向,这意味着在处理 A 时,Flask 会向自己发出一个新的资源 B 请求,并将第二个请求的结果用作用户原始请求的结果。
Are these two separate stacks, or are they both part of one stack?
这是两个独立的堆栈,还是它们都属于一个堆栈?
They are two separate stacks. However, this is an implementation detail. What's more important is not so much that there is a stack, but the fact that at any time you can get the "current" app or request (top of the stack).
它们是两个独立的堆栈。然而,这是一个实现细节。更重要的不是有一个堆栈,而是您可以随时获取“当前”应用程序或请求(堆栈顶部)的事实。
Is the request context pushed onto a stack, or is it a stack itself?
请求上下文是推送到堆栈上,还是堆栈本身?
A "request context" is one item of the "request context stack". Similarly with the "app context" and "app context stack".
“请求上下文”是“请求上下文堆栈”的一项。与“应用上下文”和“应用上下文堆栈”类似。
Am I able to push/pop multiple contexts on top of eachother? If so, why would I want to do that?
我可以在彼此之上推送/弹出多个上下文吗?如果是这样,我为什么要这样做?
In a Flask application, you typically would not do this. One example of where you might want to is for an internal redirect (described above). Even in that case, however, you would probably end up having Flask handle a new request, and so Flask would do all of the pushing/popping for you.
在 Flask 应用程序中,您通常不会这样做。您可能想要的一个示例是内部重定向(如上所述)。但是,即使在这种情况下,您最终也可能会让 Flask 处理新请求,因此 Flask 会为您完成所有推送/弹出操作。
However, there are some cases where you'd want to manipulate the stack yourself.
但是,在某些情况下,您希望自己操作堆栈。
Running code outside of a request
在请求之外运行代码
One typical problem people have is that they use the Flask-SQLAlchemy extension to set up a SQL database and model definition using code something like what is shown below...
人们遇到的一个典型问题是,他们使用 Flask-SQLAlchemy 扩展来使用如下所示的代码设置 SQL 数据库和模型定义...
app = Flask(__name__)
db = SQLAlchemy() # Initialize the Flask-SQLAlchemy extension object
db.init_app(app)
Then they use the appand dbvalues in a script that should be run from the shell. For example, a "setup_tables.py" script...
然后他们在应该从 shell 运行的脚本中使用app和db值。例如,“setup_tables.py”脚本...
from myapp import app, db
# Set up models
db.create_all()
In this case, the Flask-SQLAlchemy extension knows about the appapplication, but during create_all()it will throw an error complaining about there not being an application context. This error is justified; you never told Flask what application it should be dealing with when running the create_allmethod.
在这种情况下,Flask-SQLAlchemy 扩展知道app应用程序,但在create_all()此期间会抛出错误,抱怨没有应用程序上下文。这个错误是有道理的;你从来没有告诉 Flask 在运行这个create_all方法时它应该处理什么应用程序。
You might be wondering why you don't end up needing this with app.app_context()call when you run similar functions in your views. The reason is that Flask already handles the management of the application context for you when it is handling actual web requests. The problem really only comes up outside of these view functions (or other such callbacks), such as when using your models in a one-off script.
您可能想知道为什么with app.app_context()在视图中运行类似的函数时最终不需要此调用。原因是 Flask 在处理实际 Web 请求时已经为您处理了应用程序上下文的管理。问题实际上只出现在这些视图函数(或其他此类回调)之外,例如在一次性脚本中使用您的模型时。
The resolution is to push the application context yourself, which can be done by doing...
解决办法是自己推送应用上下文,可以通过做...
from myapp import app, db
# Set up models
with app.app_context():
db.create_all()
This will push a new application context (using the application of app, remember there could be more than one application).
这将推送一个新的应用程序上下文(使用 的应用程序app,请记住可能有多个应用程序)。
Testing
测试
Another case where you would want to manipulate the stack is for testing. You could create a unit test that handles a request and you check the results:
您想要操作堆栈的另一种情况是用于测试。您可以创建一个处理请求的单元测试并检查结果:
import unittest
from flask import request
class MyTest(unittest.TestCase):
def test_thing(self):
with app.test_request_context('/?next=http://example.com/') as ctx:
# You can now view attributes on request context stack by using `request`.
# Now the request context stack is empty
回答by tbicr
Little addition @Mark Hildreth's answer.
小补充@Mark Hildreth的回答。
Context stack look like {thread.get_ident(): []}, where []called "stack" because used only append(push), popand [-1](__getitem__(-1)) operations. So context stack will keep actual data for thread or greenlet thread.
上下文堆栈看起来像{thread.get_ident(): []},其中[]称为“堆栈”,因为仅使用append( push)pop和[-1]( __getitem__(-1)) 操作。因此上下文堆栈将保留线程或 greenlet 线程的实际数据。
current_app, g, request, sessionand etc is LocalProxyobject which just overrided special methods __getattr__, __getitem__, __call__, __eq__and etc. and return value from context stack top ([-1]) by argument name (current_app, requestfor example).
LocalProxyneeded to import this objects once and they will not miss actuality. So better just import requestwhere ever you are in code instead play with sending request argument down to you functions and methods. You can easy write own extensions with it, but do not forget that frivolous usage can make code more difficult for understanding.
current_app,g,request,session和等是LocalProxy刚刚overrided特殊的方法对象__getattr__,__getitem__,__call__,__eq__等,并从上下文堆栈顶部(返回值[-1])的参数名(current_app,request例如)。
LocalProxy需要导入一次这个对象,他们不会错过现实。所以最好只request在代码中的任何地方导入,而不是将请求参数发送给您的函数和方法。您可以使用它轻松编写自己的扩展,但不要忘记轻率的使用会使代码更难以理解。
Spend time to understand https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/local.py.
花时间了解https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/local.py。
So how populated both stacks? On request Flask:
那么如何填充两个堆栈?应要求Flask:
- create
request_contextby environment (initmap_adapter, match path) - enter or push this request:
- clear previous
request_context - create
app_contextif it missed and pushed to application context stack - this request pushed to request context stack
- init session if it missed
- clear previous
- dispatch request
- clear request and pop it from stack
request_context按环境创建(initmap_adapter,匹配路径)- 输入或推送此请求:
- 清除以前的
request_context - 创建
app_context如果错过了,推到应用程序上下文堆 - 此请求推送到请求上下文堆栈
- init session 如果它错过了
- 清除以前的
- 调度请求
- 清除请求并将其从堆栈中弹出
回答by Michael Ekoka
Previous answers already give a nice overview of what goes on in the background of Flask during a request. If you haven't read it yet I recommend @MarkHildreth's answer prior to reading this. In short, a new context (thread) is created for each http request, which is why it's necessary to have a thread Localfacility that allows objects such as requestand gto be accessible globally across threads, while maintaining their request specific context. Furthermore, while processing an http request Flask can emulate additional requests from within, hence the necessity to store their respective context on a stack. Also, Flask allows multiple wsgi applications to run along each other within a single process, and more than one can be called to action during a request (each request creates a new application context), hence the need for a context stack for applications. That's a summary of what was covered in previous answers.
以前的答案已经很好地概述了请求期间 Flask 背景中发生的事情。如果您还没有阅读它,我建议您在阅读本文之前阅读@MarkHildreth 的答案。简而言之,为每个 http 请求创建一个新的上下文(线程),这就是为什么需要有一个线程Local设施来允许诸如request和g可跨线程全局访问,同时保持其请求特定的上下文。此外,在处理 http 请求时,Flask 可以模拟来自内部的其他请求,因此有必要将它们各自的上下文存储在堆栈中。此外,Flask 允许多个 wsgi 应用程序在单个进程中相互运行,并且在请求期间可以调用多个操作(每个请求创建一个新的应用程序上下文),因此需要应用程序的上下文堆栈。这是之前答案中涵盖的内容的摘要。
My goal now is to complement our current understanding by explaining howFlask and Werkzeug do what they do with these context locals. I simplified the code to enhance the understanding of its logic, but if you get this, you should be able to easily grasp most of what's in the actual source (werkzeug.localand flask.globals).
我现在的目标是通过解释Flask 和 Werkzeug如何处理这些上下文本地变量来补充我们目前的理解。我简化了代码以增强对其逻辑的理解,但是如果您了解它,您应该能够轻松掌握实际源代码(werkzeug.local和flask.globals)中的大部分内容。
Let's first understand how Werkzeug implements thread Locals.
我们先来了解一下 Werkzeug 是如何实现线程 Locals 的。
Local
当地的
When an http request comes in, it is processed within the context of a single thread. As an alternative mean to spawn a new context during an http request, Werkzeug also allows the use of greenlets (a sort of lighter "micro-threads") instead of normal threads. If you don't have greenlets installed it will revert to using threads instead. Each of these threads (or greenlets) are identifiable by a unique id, which you can retrieve with the module's get_ident()function. That function is the starting point to the magic behind having request, current_app,url_for, g, and other such context-bound global objects.
当一个 http 请求进来时,它在单个线程的上下文中被处理。作为在 http 请求期间生成新上下文的另一种方法,Werkzeug 还允许使用 greenlets(一种更轻的“微线程”)而不是普通线程。如果您没有安装 greenlets,它将恢复使用线程。这些线程(或 greenlets)中的每一个都可以通过唯一的 id 进行识别,您可以使用模块的get_ident()函数来检索它。这功能是出发点,以有背后的魔力request,current_app,url_for,g,等这样的背景下,结合全局对象。
try:
from greenlet import get_ident
except ImportError:
from thread import get_ident
Now that we have our identity function we can know which thread we're on at any given time and we can create what's called a thread Local, a contextual object that can be accessed globally, but when you access its attributes they resolve to their value for that specific thread.
e.g.
现在我们有了标识函数,我们可以知道在任何给定时间我们在哪个线程上,我们可以创建所谓的线程Local,一个可以全局访问的上下文对象,但是当您访问它的属性时,它们会解析为它们的值那个特定的线程。例如
# globally
local = Local()
# ...
# on thread 1
local.first_name = 'John'
# ...
# on thread 2
local.first_name = 'Debbie'
Both values are present on the globally accessible Localobject at the same time, but accessing local.first_namewithin the context of thread 1 will give you 'John', whereas it will return 'Debbie'on thread 2.
这两个值同时存在于全局可访问Local对象上,但local.first_name在线程 1 的上下文中访问将给您'John',而它将'Debbie'在线程 2 上返回。
How is that possible? Let's look at some (simplified) code:
这怎么可能?让我们看一些(简化的)代码:
class Local(object)
def __init__(self):
self.storage = {}
def __getattr__(self, name):
context_id = get_ident() # we get the current thread's or greenlet's id
contextual_storage = self.storage.setdefault(context_id, {})
try:
return contextual_storage[name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
context_id = get_ident()
contextual_storage = self.storage.setdefault(context_id, {})
contextual_storage[name] = value
def __release_local__(self):
context_id = get_ident()
self.storage.pop(context_id, None)
local = Local()
From the code above we can see that the magic boils down to get_ident()which identifies the current greenlet or thread. The Localstorage then just uses that as a key to store any data contextual to the current thread.
从上面的代码我们可以看出,魔术归结为get_ident()标识当前的greenlet或线程。然后,Local存储仅使用它作为键来存储与当前线程相关的任何数据。
You can have multiple Localobjects per process and request, g, current_appand others could simply have been created like that. But that's not how it's done in Flask in which these are not technicallyLocalobjects, but more accurately LocalProxyobjects. What's a LocalProxy?
Local每个进程可以有多个对象request,g,current_app和其他对象可以像这样创建。但这不是在 Flask 中的做法,在 Flask 中,这些不是技术上的Local对象,而是更准确的LocalProxy对象。什么是LocalProxy?
LocalProxy
本地代理
A LocalProxy is an object that queries a Localto find another object of interest (i.e. the object it proxies to). Let's take a look to understand:
LocalProxy 是一个对象,它查询 aLocal以找到另一个感兴趣的对象(即它代理的对象)。我们来看看就明白了:
class LocalProxy(object):
def __init__(self, local, name):
# `local` here is either an actual `Local` object, that can be used
# to find the object of interest, here identified by `name`, or it's
# a callable that can resolve to that proxied object
self.local = local
# `name` is an identifier that will be passed to the local to find the
# object of interest.
self.name = name
def _get_current_object(self):
# if `self.local` is truly a `Local` it means that it implements
# the `__release_local__()` method which, as its name implies, is
# normally used to release the local. We simply look for it here
# to identify which is actually a Local and which is rather just
# a callable:
if hasattr(self.local, '__release_local__'):
try:
return getattr(self.local, self.name)
except AttributeError:
raise RuntimeError('no object bound to %s' % self.name)
# if self.local is not actually a Local it must be a callable that
# would resolve to the object of interest.
return self.local(self.name)
# Now for the LocalProxy to perform its intended duties i.e. proxying
# to an underlying object located somewhere in a Local, we turn all magic
# methods into proxies for the same methods in the object of interest.
@property
def __dict__(self):
try:
return self._get_current_object().__dict__
except RuntimeError:
raise AttributeError('__dict__')
def __repr__(self):
try:
return repr(self._get_current_object())
except RuntimeError:
return '<%s unbound>' % self.__class__.__name__
def __bool__(self):
try:
return bool(self._get_current_object())
except RuntimeError:
return False
# ... etc etc ...
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
def __delitem__(self, key):
del self._get_current_object()[key]
# ... and so on ...
__setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
__delattr__ = lambda x, n: delattr(x._get_current_object(), n)
__str__ = lambda x: str(x._get_current_object())
__lt__ = lambda x, o: x._get_current_object() < o
__le__ = lambda x, o: x._get_current_object() <= o
__eq__ = lambda x, o: x._get_current_object() == o
# ... and so forth ...
Now to create globally accessible proxies you would do
现在要创建全局可访问的代理,你会做
# this would happen some time near application start-up
local = Local()
request = LocalProxy(local, 'request')
g = LocalProxy(local, 'g')
and now some time early over the course of a request you would store some objects inside the local that the previously created proxies can access, no matter which thread we're on
现在在请求过程中的某个时间,您将在本地存储一些先前创建的代理可以访问的对象,无论我们在哪个线程上
# this would happen early during processing of an http request
local.request = RequestContext(http_environment)
local.g = SomeGeneralPurposeContainer()
The advantage of using LocalProxyas globally accessible objects rather than making them Localsthemselves is that it simplifies their management. You only just need a single Localobject to create many globally accessible proxies. At the end of the request, during cleanup, you simply release the one Local(i.e. you pop the context_id from its storage) and don't bother with the proxies, they're still globally accessible and still defer to the one Localto find their object of interest for subsequent http requests.
LocalProxy用作全局可访问对象而不是使它们Locals自己制作的优点是它简化了它们的管理。您只需要一个Local对象就可以创建许多全局可访问的代理。在请求结束时,在清理过程中,您只需释放一个Local(即从其存储中弹出 context_id)并且不要打扰代理,它们仍然是全局可访问的,并且仍然服从Local于找到它们的对象对后续 http 请求感兴趣。
# this would happen some time near the end of request processing
release(local) # aka local.__release_local__()
To simplify the creation of a LocalProxywhen we already have a Local, Werkzeug implements the Local.__call__()magic method as follows:
LocalProxy当我们已经有了 a 时Local,为了简化 a 的创建,Werkzeug 实现了Local.__call__()魔术方法,如下所示:
class Local(object):
# ...
# ... all same stuff as before go here ...
# ...
def __call__(self, name):
return LocalProxy(self, name)
# now you can do
local = Local()
request = local('request')
g = local('g')
However, if you look in the Flask source (flask.globals) that's still not how request, g, current_appand sessionare created. As we've established, Flask can spawn multiple "fake" requests (from a single true http request) and in the process also push multiple application contexts. This isn't a common use-case, but it's a capability of the framework. Since these "concurrent" requests and apps are still limited to run with only one having the "focus" at any time, it makes sense to use a stack for their respective context. Whenever a new request is spawned or one of the applications is called, they push their context at the top of their respective stack. Flask uses LocalStackobjects for this purpose. When they conclude their business they pop the context out of the stack.
但是,如果你在烧瓶中源(flask.globals)这仍然没有怎么看request,g,current_app和session创建。正如我们所确定的,Flask 可以产生多个“假”请求(来自单个真实的 http 请求),并且在此过程中还会推送多个应用程序上下文。这不是一个常见的用例,但它是框架的一项功能。由于这些“并发”请求和应用程序仍然仅限于在任何时候只有一个具有“焦点”的运行,因此为它们各自的上下文使用堆栈是有意义的。每当产生新请求或调用其中一个应用程序时,它们都会将上下文推送到各自堆栈的顶部。FlaskLocalStack为此使用对象。当他们结束他们的业务时,他们从堆栈中弹出上下文。
LocalStack
本地堆栈
This is what a LocalStacklooks like (again the code is simplified to facilitate understanding of its logic).
这就是 a 的LocalStack样子(再次简化了代码以方便理解其逻辑)。
class LocalStack(object):
def __init__(self):
self.local = Local()
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, 'stack', None)
if stack is None:
return None
elif len(stack) == 1:
release_local(self.local) # this simply releases the 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
Note from the above that a LocalStackis a stack stored in a local, not a bunch of locals stored on a stack. This implies that although the stack is globally accessible it's a different stack in each thread.
请注意,aLocalStack是存储在本地的堆栈,而不是存储在堆栈上的一堆本地人。这意味着尽管堆栈是全局可访问的,但它在每个线程中都是不同的堆栈。
Flask doesn't have its request, current_app, g, and sessionobjects resolving directly to a LocalStack, it rather uses LocalProxyobjects that wrap a lookup function (instead of a Localobject) that will find the underlying object from the LocalStack:
Flask 没有将其request, current_app,g和session对象直接解析为 a LocalStack,而是使用LocalProxy包装查找函数的Local对象(而不是对象),该函数将从 中找到底层对象LocalStack:
_request_ctx_stack = LocalStack()
def _find_request():
top = _request_ctx_stack.top
if top is None:
raise RuntimeError('working outside of request context')
return top.request
request = LocalProxy(_find_request)
def _find_session():
top = _request_ctx_stack.top
if top is None:
raise RuntimeError('working outside of request context')
return top.session
session = LocalProxy(_find_session)
_app_ctx_stack = LocalStack()
def _find_g():
top = _app_ctx_stack.top
if top is None:
raise RuntimeError('working outside of application context')
return top.g
g = LocalProxy(_find_g)
def _find_app():
top = _app_ctx_stack.top
if top is None:
raise RuntimeError('working outside of application context')
return top.app
current_app = LocalProxy(_find_app)
All these are declared at application start-up, but do not actually resolve to anything until a request context or application context is pushed to their respective stack.
所有这些都是在应用程序启动时声明的,但在将请求上下文或应用程序上下文推送到它们各自的堆栈之前,实际上不会解析任何内容。
If you're curious to see how a context is actually inserted in the stack (and subsequently popped out), look in flask.app.Flask.wsgi_app()which is the point of entry of the wsgi app (i.e. what the web server calls and pass the http environment to when a request comes in), and follow the creation of the RequestContextobject all through its subsequent push()into _request_ctx_stack. Once pushed at the top of the stack, it's accessible via _request_ctx_stack.top. Here's some abbreviated code to demonstrate the flow:
如果您想知道上下文实际上是如何插入堆栈中的(然后弹出),请查看flask.app.Flask.wsgi_app()wsgi 应用程序的入口点(即 Web 服务器调用的内容并将 http 环境传递给request 进来),并在RequestContext对象的创建过程中一直跟随它的后续push()into _request_ctx_stack。一旦被推到堆栈的顶部,它就可以通过_request_ctx_stack.top. 下面是一些用于演示流程的缩写代码:
So you start an app and make it available to the WSGI server...
因此,您启动了一个应用程序并使其可用于 WSGI 服务器...
app = Flask(*config, **kwconfig)
# ...
Later an http request comes in and the WSGI server calls the app with the usual params...
后来一个 http 请求进来,WSGI 服务器用通常的参数调用应用程序......
app(environ, start_response) # aka app.__call__(environ, start_response)
This is roughly what happens in the app...
这大致是应用程序中发生的事情......
def Flask(object):
# ...
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)
def wsgi_app(self, environ, start_response):
ctx = RequestContext(self, environ)
ctx.push()
try:
# process the request here
# raise error if any
# return Response
finally:
ctx.pop()
# ...
and this is roughly what happens with RequestContext...
这大概是 RequestContext 发生的事情......
class RequestContext(object):
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.session = self.app.open_session(self.request)
if self.session is None:
self.session = self.app.make_null_session()
self.flashes = None
def push(self):
_request_ctx_stack.push(self)
def pop(self):
_request_ctx_stack.pop()
Say a request has finished initializing, the lookup for request.pathfrom one of your view functions would therefore go as follow:
假设请求已完成初始化,因此request.path从您的视图函数之一进行查找将如下所示:
- start from the globally accessible
LocalProxyobjectrequest. - to find its underlying object of interest (the object it's proxying to) it calls its lookup function
_find_request()(the function it registered as itsself.local). - that function queries the
LocalStackobject_request_ctx_stackfor the top context on the stack. - to find the top context, the
LocalStackobject first queries its innerLocalattribute (self.local) for thestackproperty that was previously stored there. - from the
stackit gets the top context - and
top.requestis thus resolved as the underlying object of interest. - from that object we get the
pathattribute
- 从全局可访问
LocalProxy对象开始request。 - 为了找到它感兴趣的底层对象(它代理的对象),它调用它的查找函数
_find_request()(它注册为它的函数self.local)。 - 该函数查询堆栈顶部上下文的
LocalStack对象_request_ctx_stack。 - 为了找到顶部上下文,
LocalStack对象首先查询其内部Local属性 (self.local) 以获取stack先前存储在那里的属性。 - 从
stack它获取顶级上下文 - 并
top.request因此被解析为潜在的感兴趣对象。 - 从那个对象我们得到
path属性
So we've seen how Local, LocalProxy, and LocalStackwork, now think for a moment of the implications and nuances in retrieving the pathfrom:
因此,我们已经看到了Local、LocalProxy和 的LocalStack工作方式,现在考虑一下path从以下位置检索的含义和细微差别:
- a
requestobject that would be a simple globally accessible object. - a
requestobject that would be a local. - a
requestobject stored as an attribute of a local. - a
requestobject that is a proxy to an object stored in a local. - a
requestobject stored on a stack, that is in turn stored in a local. - a
requestobject that is a proxy to an object on a stack stored in a local. <- this is what Flask does.
- 一个
request对象,它是一个简单的全局可访问对象。 - 一个
request对象,这将是一个地方。 - 一个
request对象存储为本地的属性。 - 一个
request对象,它是存储在本地的对象的代理。 request存储在堆栈上的对象,该对象又存储在本地。- 一个
request对象,它是存储在本地的堆栈上对象的代理。<- 这就是 Flask 所做的。
回答by Ratn Deo--Dev
Lets take one example , suppose you want to set a usercontext (using flask construct of Local and LocalProxy).
让我们举一个例子,假设你想设置一个用户上下文(使用 Local 和 LocalProxy 的烧瓶构造)。
Define one User class :
定义一个 User 类:
class User(object):
def __init__(self):
self.userid = None
define a function to retrive user object inside current thread or greenlet
定义一个函数来检索当前线程或greenlet中的用户对象
def get_user(_local):
try:
# get user object in current thread or greenlet
return _local.user
except AttributeError:
# if user object is not set in current thread ,set empty user object
_local.user = User()
return _local.user
Now define a LocalProxy
现在定义一个 LocalProxy
usercontext = LocalProxy(partial(get_user, Local()))
Now to get userid of user in current thread usercontext.userid
现在在当前线程 usercontext.userid 中获取用户的用户 ID
explanation :
解释 :
1.Local has a dict of identity and objet , identity is threadid or greenlet id , in this example _local.user = User() is eqivalent to _local.___storage__[current thread's id] ["user"] = User()
1.Local有identity和objet的字典,identity是threadid或者greenlet id,在这个例子中_local.user = User()相当于_local.___storage__[current thread's id] ["user"] = User()
- LocalProxy delegatesoperation to wrapped up Local object or you can provide a function that returns target object. In above example get_user function provides current user object to LocalProxy , and when you ask for current user's userid by usercontext.userid, LocalProxy's __getattr__ function first calls get_user to get User object (user) and then calls getattr(user,"userid"). to set userid on User ( in current thread or greenlet) you simply do : usercontext.userid = "user_123"
- LocalProxy委托操作来包装 Local 对象,或者您可以提供一个返回目标对象的函数。在上面的例子中,get_user 函数向 LocalProxy 提供当前用户对象,当你通过 usercontext.userid 请求当前用户的 userid 时,LocalProxy 的 __getattr__ 函数首先调用 get_user 来获取用户对象(user),然后调用 getattr(user,"userid")。要在用户(在当前线程或 greenlet 中)设置用户 ID,您只需执行以下操作: usercontext.userid = "user_123"

