在学习falsk过程中,不可避免会遇到上下文,g,现在了深入了解一下它们的实现原理。
其实它们都是一套班子。
# flask.globals.py # 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"))
重点概念local,localstack,localproxy。
class Local(object):
__slots__ = ("__storage__", "__ident_func__")
def __init__(self):
object.__setattr__(self, "__storage__", {})
object.__setattr__(self, "__ident_func__", get_ident)
def __iter__(self):
return iter(self.__storage__.items())
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)
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__() # 获取当前线程/协程的id
storage = self.__storage__ # 具体存储信息的dict
try: # 处理ident不存在于storage中的情况
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)
它的主要属性如下:
Local().storage = {ident_1:dict1, ident_2:dict2}
ident_1/2为线程号/协程号
取值时也是根据线程号找到对应的dict.name
另外需要说明的是ident获取方法声明如下:
实现了线程与协程号获取。
try:
from greenlet import getcurrent as get_ident
except ImportError:
try:
from thread import get_ident
except ImportError:
from _thread import get_ident
它基本是local的队列版。
class LocalStack(object):
def __init__(self):
self._local = Local()
def __call__(self):
def _lookup():
rv = self.top
if rv is None:
raise RuntimeError("object unbound")
return rv
return LocalProxy(_lookup)
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)
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
为local()实例添加了_local队列存储dict对象,实现push,pop,top做存取。
应用上下文和请求上下文就是基于它的。
比较绕的是localproxy
@implements_bool
class LocalProxy(object):
__slots__ = ("__local", "__dict__", "__name__", "__wrapped__")
def __init__(self, local, name=None):
object.__setattr__(self, "_LocalProxy__local", local)
object.__setattr__(self, "__name__", name)
if callable(local) and not hasattr(local, "__release_local__"):
# "local" is a callable that is not an instance of Local or
# LocalManager: mark it as a wrapped function.
object.__setattr__(self, "__wrapped__", local)
def _get_current_object(self):
"""Return the current object. This is useful if you want the real
object behind the proxy at a time for performance reasons or because
you want to pass the object into a different context.
"""
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
def __delitem__(self, key):
del self._get_current_object()[key]
__setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
__delattr__ = lambda x, n: delattr(x._get_current_object(), n)
object.setattr(self, "_LocalProxy__local", local)等效于self.__local = local,具体参考私有变量名轧压。这么写是为了避免与下文的魔术方法重载冲突。
LocalProxy通过_get_current_object来获取代理的对象。需要注意的是当初始化参数为callable对象时,则直接调用以返回Local或LocalStack对象,具体看源代码的注释。
核心就一句 if not hasattr(self.__local, "release_local"):
return self.__local()
对于g而言,通过还是执行def _lookup_app_object(name):
localproxy实质是代理类,重载了绝大多数操作符,调用LocalProxy的相应操作时,通过_get_current_object method来获取真正代理的对象,代理了绝大多数操作。
说老实话,不是非常理解为什么非要实现这一代理,使用_app_ctx_stack.top.getattr(name)也是可以实现相同功能的。
三者是类似的,这里以g为例。
回看flask.globals
def _lookup_app_object(name):
top = _app_ctx_stack.top
if top is None:
raise RuntimeError(_app_ctx_err_msg)
return getattr(top, name)
g = LocalProxy(partial(_lookup_app_object, "g"))
结合上文localproxy所述,g实质是_app_ctx_stack.top.g
而_app_ctx_stack中所推进的对象是
class AppContext(object):
class AppContext(object):
def __init__(self, app):
self.app = app
self.url_adapter = app.create_url_adapter(None)
self.g = app.app_ctx_globals_class()
g = class _AppCtxGlobals()
它实质是一个简单的类,用于存储数据。
A plain object. Used as a namespace for storing data during an application context.
g在同一请求环境中可用的特性主要源自top = _app_ctx_stack.top,把self.g = app.app_ctx_globals_class()换成g={}也没有影响。
# flask local,localstack,localproxy,g
原文:https://www.cnblogs.com/wodeboke-y/p/13144414.html