专栏名称: Python开发者
人生苦短,我用 Python。伯乐在线旗下账号「Python开发者」分享 Python 相关的技术文章、工具资源、精选课程、热点资讯等。
目录
相关文章推荐
Python中文社区  ·  揭秘 DeepSeek ... ·  2 天前  
Python开发者  ·  DeepSeek 下棋靠忽悠赢了 ... ·  4 天前  
Python爱好者社区  ·  “给我滚出贵大!”郑强出任贵州大学校长,打算 ... ·  5 天前  
Python爱好者社区  ·  节后第一个私活,赚了3w ·  4 天前  
Python爱好者社区  ·  python接私活,yyds ·  3 天前  
51好读  ›  专栏  ›  Python开发者

flask 源码解析:上下文

Python开发者  · 公众号  · Python  · 2017-03-13 20:28

正文

(点击 上方蓝字 ,快速关注我们)


来源:cizixs

cizixs.com/2017/01/13/flask-insight-context

如有好文章投稿,请点击 → 这里了解详情


上下文(application context 和 request context)


上下文一直是计算机中难理解的概念,在知乎的一个问题下面有个很通俗易懂的回答:


每一段程序都有很多外部变量。只有像Add这种简单的函数才是没有外部变量的。一旦你的一段程序有了外部变量,这段程序就不完整,不能独立运行。你为了使他们运行,就要给所有的外部变量一个一个写一些值进去。这些值的集合就叫上下文。

— vzch


比如,在 flask 中,视图函数需要知道它执行情况的请求信息(请求的 url,参数,方法等)以及应用信息(应用中初始化的数据库等),才能够正确运行。


最直观地做法是把这些信息封装成一个对象,作为参数传递给视图函数。但是这样的话,所有的视图函数都需要添加对应的参数,即使该函数内部并没有使用到它。


flask 的做法是把这些信息作为类似全局变量的东西,视图函数需要的时候,可以使用 from flask import request 获取。但是这些对象和全局变量不同的是——它们必须是动态的,因为在多线程或者多协程的情况下,每个线程或者协程获取的都是自己独特的对象,不会互相干扰。


那么如何实现这种效果呢?如果对 python 多线程比较熟悉的话,应该知道多线程中有个非常类似的概念 threading.local,可以实现多线程访问某个变量的时候只看到自己的数据。内部的原理说起来也很简单,这个对象有一个字典,保存了线程 id 对应的数据,读取该对象的时候,它动态地查询当前线程 id 对应的数据。flaskpython 上下文的实现也类似,后面会详细解释。


flask 中有两种上下文:application context 和 request context。上下文有关的内容定义在 globals.py 文件,文件的内容也非常短:


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' ))


flask 提供两种上下文:application context 和 request context 。app lication context 又演化出来两个变量 current_app 和 g,而 request context 则演化出来 request 和 session。


这里的实现用到了两个东西:LocalStack 和 LocalProxy。它们两个的结果就是我们可以动态地获取两个上下文的内容,在并发程序中每个视图函数都会看到属于自己的上下文,而不会出现混乱。


LocalStack 和 LocalProxy 都是 werkzeug 提供的,定义在 local.py 文件中。在分析这两个类之前,我们先介绍这个文件另外一个基础的类 Local。Local 就是实现了类似 threading.local 的效果——多线程或者多协程情况下全局变量的隔离效果。下面是它的代码:


# 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_ident

except ImportError :

try :

from thread import get_ident

except ImportError :

from _thread import get_ident

class 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 )


可以看到,Local 对象内部的数据都是保存在 __storage__ 属性的,这个属性变量是个嵌套的字典:map[ident]map[key]value。最外面字典 key 是线程或者协程的 identity,value 是另外一个字典,这个内部字典就是用户自定义的 key-value 键值对。用户访问实例的属性,就变成了访问内部的字典,外面字典的 key 是自动关联的。__ident_func 是 协程的 get_current 或者线程的 get_ident,从而获取当前代码所在线程或者协程的 id。


除了这些基本操作之外,Local 还实现了 __release_local__ ,用来清空(析构)当前线程或者协程的数据(状态)。__call__ 操作来创建一个 LocalProxy 对象,LocalProxy 会在下面讲到。


理解了 Local,我们继续回来看另外两个类。


LocalStack 是基于 Local 实现的栈结构。如果说 Local 提供了多线程或者多协程隔离的属性访问,那么 LocalStack 就提供了隔离的栈访问。下面是它的实现代码,可以看到它提供了 push、pop 和 top 方法。


__release_local__ 可以用来清空当前线程或者协程的栈数据,__call__ 方法返回当前线程或者协程栈顶元素的代理对象。


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 , '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


我们在之前看到了 request context 的定义,它就是一个 LocalStack 的实例:


_request_ctx_stack = LocalStack()


它会当前线程或者协程的请求都保存在栈里,等使用的时候再从里面读取。至于为什么要用到栈结构,而不是直接使用 Local,我们会在后面揭晓答案,你可以先思考一下。


LocalProxy 是一个 Local 对象的代理,负责把所有对自己的操作转发给内部的 Local 对象。LocalProxy 的构造函数介绍一个 callable 的参数,这个 callable 调用之后需要返回一个 Local 实例,后续所有的属性操作都会转发给 callable 返回的对象。


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 )







请到「今天看啥」查看全文