在学习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