Frühere Antworten geben bereits einen schönen Überblick darüber, was während einer Anfrage im Hintergrund von Flask vor sich geht. Wenn Sie es noch nicht gelesen haben, empfehle ich die Antwort von @ MarkHildreth, bevor Sie dies lesen. Kurz gesagt, für jede http-Anforderung wird ein neuer Kontext (Thread) erstellt, weshalb eine Thread-Funktion erforderlich ist Local
, die Objekte wie request
und zulässtg
globaler Zugriff über Threads hinweg unter Beibehaltung des anforderungsspezifischen Kontexts. Darüber hinaus kann Flask während der Verarbeitung einer http-Anfrage zusätzliche Anfragen von innen emulieren, weshalb der jeweilige Kontext auf einem Stapel gespeichert werden muss. Außerdem ermöglicht Flask, dass mehrere wsgi-Anwendungen innerhalb eines einzelnen Prozesses nebeneinander ausgeführt werden, und während einer Anforderung können mehrere aufgerufen werden (jede Anforderung erstellt einen neuen Anwendungskontext), sodass ein Kontextstapel für Anwendungen erforderlich ist. Das ist eine Zusammenfassung dessen, was in früheren Antworten behandelt wurde.
Mein Ziel ist es nun, unser derzeitiges Verständnis zu ergänzen, indem ich erkläre, wie Flask und Werkzeug das tun, was sie mit diesen Kontext-Einheimischen tun. Ich habe den Code vereinfacht, um das Verständnis seiner Logik zu verbessern, aber wenn Sie dies erhalten, sollten Sie in der Lage sein, die meisten Inhalte der tatsächlichen Quelle ( werkzeug.local
und flask.globals
) leicht zu erfassen .
Lassen Sie uns zunächst verstehen, wie Werkzeug Thread-Locals implementiert.
Lokal
Wenn eine http-Anfrage eingeht, wird sie im Kontext eines einzelnen Threads verarbeitet. Als alternatives Mittel, um während einer http-Anfrage einen neuen Kontext zu erzeugen, erlaubt Werkzeug auch die Verwendung von Greenlets (eine Art leichterer "Mikro-Threads") anstelle von normalen Threads. Wenn Sie keine Greenlets installiert haben, werden stattdessen wieder Threads verwendet. Jeder dieser Threads (oder Greenlets) ist durch eine eindeutige ID erkennbar, die Sie mit der get_ident()
Funktion des Moduls abrufen können. Diese Funktion ist der Ausgangspunkt , um die Magie hinter mit request
, current_app
, url_for
, g
und andere solcher kontextgebundene globalen Objekten.
try:
from greenlet import get_ident
except ImportError:
from thread import get_ident
Nachdem wir nun unsere Identitätsfunktion haben, können wir wissen, in welchem Thread wir uns zu einem bestimmten Zeitpunkt befinden, und wir können einen sogenannten Thread erstellen Local
, ein Kontextobjekt, auf das global zugegriffen werden kann. Wenn Sie jedoch auf seine Attribute zugreifen, werden sie in ihren Wert für aufgelöst dieser spezifische Thread. z.B
# globally
local = Local()
# ...
# on thread 1
local.first_name = 'John'
# ...
# on thread 2
local.first_name = 'Debbie'
Beide Werte sind gleichzeitig auf dem global zugänglichen Local
Objekt vorhanden, aber der Zugriff local.first_name
im Kontext von Thread 1 gibt Ihnen die Möglichkeit 'John'
, während er 'Debbie'
auf Thread 2 zurückkehrt.
Wie ist das möglich? Schauen wir uns einen (vereinfachten) Code an:
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()
Aus dem obigen Code können wir erkennen, dass die Magie darauf get_ident()
hinausläuft, das aktuelle Greenlet oder den aktuellen Thread zu identifizieren. Der Local
Speicher verwendet dies dann nur als Schlüssel zum Speichern von Daten, die für den aktuellen Thread kontextbezogen sind.
Sie können mehrere Local
Objekte pro Prozess und request
haben g
, current_app
und andere könnten einfach so erstellt worden sein. Aber so wird es in Flask nicht gemacht , wo es sich nicht um technische Local
Objekte handelt, sondern um genauere LocalProxy
Objekte. Was ist ein LocalProxy
?
LocalProxy
Ein LocalProxy ist ein Objekt, das a abfragt Local
, um ein anderes interessierendes Objekt zu finden (dh das Objekt, für das es einen Proxy erstellt). Lassen Sie uns einen Blick darauf werfen, um zu verstehen:
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 ...
Nun würden Sie global zugängliche Proxys erstellen
# this would happen some time near application start-up
local = Local()
request = LocalProxy(local, 'request')
g = LocalProxy(local, 'g')
und jetzt, einige Zeit früher im Verlauf einer Anfrage, würden Sie einige Objekte in der lokalen speichern, auf die die zuvor erstellten Proxys zugreifen können, unabhängig davon, auf welchem Thread wir uns befinden
# this would happen early during processing of an http request
local.request = RequestContext(http_environment)
local.g = SomeGeneralPurposeContainer()
Der Vorteil der Verwendung LocalProxy
als global zugängliche Objekte, anstatt sie Locals
selbst zu erstellen, besteht darin, dass ihre Verwaltung vereinfacht wird. Sie benötigen nur ein einziges Local
Objekt, um viele global zugängliche Proxys zu erstellen. Am Ende der Anforderung geben Sie während der Bereinigung einfach die Freigabe frei Local
(dh Sie löschen die context_id aus ihrem Speicher) und kümmern sich nicht um die Proxys. Sie sind weiterhin global zugänglich und verschieben sich immer noch auf diejenige Local
, um ihr Objekt zu finden von Interesse für nachfolgende http-Anfragen.
# this would happen some time near the end of request processing
release(local) # aka local.__release_local__()
Um die Erstellung eines zu vereinfachen, LocalProxy
wenn wir bereits ein haben Local
, implementiert Werkzeug die Local.__call__()
magische Methode wie folgt:
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')
Doch wenn man sich in der Flasche Quelle (flask.globals) , das ist immer noch nicht , wie request
, g
, current_app
und session
erstellt werden. Wie wir festgestellt haben, kann Flask mehrere "gefälschte" Anforderungen (aus einer einzigen echten http-Anforderung) erzeugen und dabei auch mehrere Anwendungskontexte übertragen. Dies ist kein allgemeiner Anwendungsfall, aber es ist eine Fähigkeit des Frameworks. Da diese "gleichzeitigen" Anforderungen und Apps immer noch nur mit einem "Fokus" ausgeführt werden können, ist es sinnvoll, einen Stapel für den jeweiligen Kontext zu verwenden. Immer wenn eine neue Anforderung erzeugt oder eine der Anwendungen aufgerufen wird, verschieben sie ihren Kontext an die Spitze ihres jeweiligen Stapels. Flask verwendet LocalStack
zu diesem Zweck Objekte. Wenn sie ihr Geschäft abschließen, entfernen sie den Kontext aus dem Stapel.
LocalStack
So LocalStack
sieht ein aus (wieder wird der Code vereinfacht, um das Verständnis seiner Logik zu erleichtern).
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
Beachten Sie, dass a LocalStack
ein Stapel ist, der in einem lokalen Speicher gespeichert ist, nicht eine Gruppe von Einheimischen, die auf einem Stapel gespeichert sind. Dies bedeutet, dass der Stapel zwar global zugänglich ist, in jedem Thread jedoch ein anderer Stapel vorhanden ist.
Kolben haben nicht seine request
, current_app
, g
, und session
direkt an einen Auflösungs Objekte LocalStack
, es eher verwendet LocalProxy
Objekte , welche eine Lookup - Funktion (anstelle einem Local
Objekt) , die das darunter liegende Objekt aus dem finden wird 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)
Alle diese werden beim Start der Anwendung deklariert, werden jedoch erst dann aufgelöst, wenn ein Anforderungs- oder Anwendungskontext auf den jeweiligen Stapel verschoben wird.
Wenn Sie neugierig sind, wie ein Kontext tatsächlich in den Stapel eingefügt wird (und anschließend herausspringt), schauen Sie, an flask.app.Flask.wsgi_app()
welcher Stelle die wsgi-App eingegeben wird (dh was der Webserver aufruft, und übergeben Sie die http-Umgebung an wann a Anfrage kommt herein), und folgen Sie der Erstellung des RequestContext
Objekts durch seine anschließende push()
in _request_ctx_stack
. Sobald es oben auf den Stapel geschoben wurde, ist es über zugänglich _request_ctx_stack.top
. Hier ist ein abgekürzter Code, um den Ablauf zu demonstrieren:
Sie starten also eine App und stellen sie dem WSGI-Server zur Verfügung ...
app = Flask(*config, **kwconfig)
# ...
Später kommt eine http-Anfrage und der WSGI-Server ruft die App mit den üblichen Parametern auf ...
app(environ, start_response) # aka app.__call__(environ, start_response)
Dies ist ungefähr das, was in der App passiert ...
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()
# ...
und genau das passiert mit 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()
Angenommen, eine Anfrage wurde initialisiert. Die Suche nach request.path
einer Ihrer Ansichtsfunktionen würde daher wie folgt aussehen:
- Beginnen Sie mit dem global zugänglichen
LocalProxy
Objekt request
.
- Um das zugrunde liegende interessierende Objekt (das Objekt, auf das es sich bezieht) zu finden, ruft es seine Suchfunktion auf
_find_request()
(die Funktion, die es als seine registriert hat self.local
).
- Diese Funktion fragt das
LocalStack
Objekt _request_ctx_stack
nach dem obersten Kontext auf dem Stapel ab.
- Um den obersten Kontext zu finden,
LocalStack
fragt das Objekt zuerst sein inneres Local
Attribut ( self.local
) nach der stack
Eigenschaft ab, die zuvor dort gespeichert wurde.
- von dem
stack
bekommt es den obersten Kontext
- und
top.request
wird somit als das zugrunde liegende interessierende Objekt aufgelöst.
- Von diesem Objekt erhalten wir das
path
Attribut
So wir , wie gesehen haben Local
, LocalProxy
und LocalStack
Arbeit, denkt jetzt für einen Moment der Auswirkungen und Nuancen in dem Abrufen der path
von:
- Ein
request
Objekt, das ein einfaches global zugängliches Objekt wäre.
- ein
request
Objekt, das ein lokales wäre.
- Ein
request
Objekt, das als Attribut eines lokalen Objekts gespeichert ist.
- Ein
request
Objekt, das ein Proxy für ein Objekt ist, das in einem lokalen Objekt gespeichert ist.
- Ein
request
Objekt, das auf einem Stapel gespeichert ist, der wiederum in einem lokalen Objekt gespeichert ist.
- Ein
request
Objekt, das ein Proxy für ein Objekt auf einem Stapel ist, der in einem lokalen Speicher gespeichert ist. <- das macht Flask.