• Flask请求和应用上下文源码分析


     

     

    flask的request和session设置方式比较新颖,如果没有这种方式,那么就只能通过参数的传递。

    flask是如何做的呢?

    1:本地线程,保证即使是多个线程,自己的值也是互相隔离

    复制代码
     1 import threading
     2  
     3 local_values = threading.local()
     4  
     5  
     6 def func(num):
     7     local_values.name = num
     8     import time
     9     time.sleep(1)
    10     print(local_values.name, threading.current_thread().name)
    11  
    12  
    13 for i in range(20):
    14     th = threading.Thread(target=func, args=(i,), name='线程%s' % i)
    15     th.start()
    复制代码

    2:自定义threading.local

    复制代码
     1 """
     2 {
     3    1368:{}
     4 }
     5 
     6 
     7 
     8 """
     9 import threading
    10 try:
    11     from greenlet import getcurrent as get_ident # 协程
    12 except ImportError:
    13     try:
    14         from thread import get_ident
    15     except ImportError:
    16         from _thread import get_ident # 线程
    17 
    18 
    19 class Local(object):
    20     def __init__(self):
    21         self.storage = {}
    22         self.get_ident = get_ident
    23 
    24     def set(self,k,v):
    25         ident = self.get_ident()
    26         origin = self.storage.get(ident)
    27         if not origin:
    28             origin = {k:v}
    29         else:
    30             origin[k] = v
    31         self.storage[ident] = origin
    32 
    33     def get(self,k):
    34         ident = self.get_ident()
    35         origin = self.storage.get(ident)
    36         if not origin:
    37             return None
    38         return origin.get(k,None)
    39 
    40 local_values = Local()
    41 
    42 
    43 def task(num):
    44     local_values.set('name',num)
    45     import time
    46     time.sleep(1)
    47     print(local_values.get('name'), threading.current_thread().name)
    48 
    49 
    50 for i in range(20):
    51     th = threading.Thread(target=task, args=(i,),name='线程%s' % i)
    52     th.start()
    复制代码

    升级版

    复制代码
     1 import threading
     2 try:
     3     from greenlet import getcurrent as get_ident # 协程
     4 except ImportError:
     5     try:
     6         from thread import get_ident
     7     except ImportError:
     8         from _thread import get_ident # 线程
     9 
    10 
    11 class Local(object):
    12 
    13     def __init__(self):
    14         object.__setattr__(self, '__storage__', {})
    15         object.__setattr__(self, '__ident_func__', get_ident)
    16 
    17 
    18     def __getattr__(self, name):
    19         try:
    20             return self.__storage__[self.__ident_func__()][name]
    21         except KeyError:
    22             raise AttributeError(name)
    23 
    24     def __setattr__(self, name, value):
    25         ident = self.__ident_func__()
    26         storage = self.__storage__
    27         try:
    28             storage[ident][name] = value
    29         except KeyError:
    30             storage[ident] = {name: value}
    31 
    32     def __delattr__(self, name):
    33         try:
    34             del self.__storage__[self.__ident_func__()][name]
    35         except KeyError:
    36             raise AttributeError(name)
    37 
    38 
    39 local_values = Local()
    40 
    41 
    42 def task(num):
    43     local_values.name = num
    44     import time
    45     time.sleep(1)
    46     print(local_values.name, threading.current_thread().name)
    47 
    48 
    49 for i in range(20):
    50     th = threading.Thread(target=task, args=(i,),name='线程%s' % i)
    51     th.start()
    复制代码

    说明解释:

    复制代码
     1 - threading.local对象,用于为每个线程开辟一块空间来保存它独有的值。
     2         
     3         - 源码(request)
     4             - 情况一:单进程单线程,基于全局变量做。
     5             - 情况二:单进程多线程,threading.local对象。
     6             - 情况二:单进程单线程(多个协程),threading.local对象做不到。
     7             
     8         - 决定:
     9             - 以后不支持协程:threading.local对象。
    10             - 支持:自定义类似threading.local对象(支持协程)
    11         - 自定义类似threading.local对象
    12           PS: 
    13             a. 
    14                 object.__setattr__(self, 'storage', {})
    15                 self.storage = {}
    16             b.
    17                 对象.xx
    18                  def __setattr__(self, key, value):
    19                     print(key,value)
    复制代码

    3:请求上下文原理

    复制代码
     1 from functools import partial
     2 from flask.globals import LocalStack, LocalProxy
     3  
     4 ls = LocalStack()
     5  
     6  
     7 class RequestContext(object):
     8     def __init__(self, environ):
     9         self.request = environ
    10  
    11  
    12 def _lookup_req_object(name):
    13     top = ls.top
    14     if top is None:
    15         raise RuntimeError(ls)
    16     return getattr(top, name)
    17  
    18  
    19 session = LocalProxy(partial(_lookup_req_object, 'request'))
    20  
    21 ls.push(RequestContext('c1')) # 当请求进来时,放入
    22 print(session) # 视图函数使用
    23 print(session) # 视图函数使用
    24 ls.pop() # 请求结束pop
    25  
    26  
    27 ls.push(RequestContext('c2'))
    28 print(session)
    29  
    30 ls.push(RequestContext('c3'))
    31 print(session)
    复制代码

    4:请求上下文源码

    复制代码
     1 from greenlet import getcurrent as get_ident
     2  
     3  
     4 def release_local(local):
     5     local.__release_local__()
     6  
     7  
     8 class Local(object):
     9     __slots__ = ('__storage__', '__ident_func__')
    10  
    11     def __init__(self):
    12         # self.__storage__ = {}
    13         # self.__ident_func__ = get_ident
    14         object.__setattr__(self, '__storage__', {})
    15         object.__setattr__(self, '__ident_func__', get_ident)
    16  
    17     def __release_local__(self):
    18         self.__storage__.pop(self.__ident_func__(), None)
    19  
    20     def __getattr__(self, name):
    21         try:
    22             return self.__storage__[self.__ident_func__()][name]
    23         except KeyError:
    24             raise AttributeError(name)
    25  
    26     def __setattr__(self, name, value):
    27         ident = self.__ident_func__()
    28         storage = self.__storage__
    29         try:
    30             storage[ident][name] = value
    31         except KeyError:
    32             storage[ident] = {name: value}
    33  
    34     def __delattr__(self, name):
    35         try:
    36             del self.__storage__[self.__ident_func__()][name]
    37         except KeyError:
    38             raise AttributeError(name)
    39  
    40  
    41 class LocalStack(object):
    42     def __init__(self):
    43         self._local = Local()
    44  
    45     def __release_local__(self):
    46         self._local.__release_local__()
    47  
    48     def push(self, obj):
    49         """Pushes a new item to the stack"""
    50         rv = getattr(self._local, 'stack', None)
    51         if rv is None:
    52             self._local.stack = rv = []
    53         rv.append(obj)
    54         return rv
    55  
    56     def pop(self):
    57         """Removes the topmost item from the stack, will return the
    58         old value or `None` if the stack was already empty.
    59         """
    60         stack = getattr(self._local, 'stack', None)
    61         if stack is None:
    62             return None
    63         elif len(stack) == 1:
    64             release_local(self._local)
    65             return stack[-1]
    66         else:
    67             return stack.pop()
    68  
    69     @property
    70     def top(self):
    71         """The topmost item on the stack.  If the stack is empty,
    72         `None` is returned.
    73         """
    74         try:
    75             return self._local.stack[-1]
    76         except (AttributeError, IndexError):
    77             return None
    78  
    79  
    80 stc = LocalStack()
    81  
    82 stc.push(123)
    83 v = stc.pop()
    84  
    85 print(v)
    复制代码

    5:个人源码剖析

    a.偏函数

    复制代码
     1 import functools
     2 
     3 def func(a1):
     4     print(a1)
     5 
     6 
     7 new_func = functools.partial(func,666)
     8 
     9 new_func()
    10 
    11 说明:将666这个参数当做func函数的第一个参数
    复制代码

    b.第一阶段

    复制代码
    1 1.当携带这用户的请求过来之后,首先会执行app.run,会执行 run_simple(host, port, self, **options),因为run方法是由app调用的,app又是Flask的对象,因此当执行了app.run()之后就会调用Flask类中的__call__方法
    2 2.进入app.__call__拿到return self.wsgi_app(environ, start_response)
    3 3.进入app.wsgi_app之后就会执行:ctx = self.request_context(environ)
    4 app.request_context(environ)会返回 return RequestContext(self, environ);然后执行__init__方法得到request = app.request_class(environ);最后将请求的信息放入request中。经过上面的操作,得出ctx就是携带有请求信息的request_context对象,ctx里面具有ctx.app,ctx.request,ctx.session
    5 4.然后执行ctx.push()
    6 进入push()里面找到 _request_ctx_stack.push(self),这里面的self指的是ctx对象,_request_ctx_stack是全局变量,_request_ctx_stack = LocalStack(),_request_ctx_stack 是LocalStack这个类的对象;通过def push(self, obj)这个方法将ctx这个对象存入threadinglocal中,给ctx这个对象分配专属与他的一块内存空间。
    复制代码

    c.第二阶段

    复制代码
     1 如果需要打印print(request),源码是如何执行的呢?
     2 进入request
     3 1.request = LocalProxy(partial(_lookup_req_object, 'request'))
     4 这个里面:偏函数=partial(_lookup_req_object, 'request')
     5 首先执行LocalProxy这个类实例化执行__init__方法:
     6 def __init__(self, local, name=None):
     7       object.__setattr__(self, '_LocalProxy__local', local)
     8 在这里:_LocalProxy__local其实就是强制获取私有字段等价于
     9 self._local = local
    10 2.再执行__str__方法:
    11  __str__ = lambda x: str(x._get_current_object())
    12 3.def _get_current_object(self):
    13         if not hasattr(self.__local, '__release_local__'):
    14             return self.__local()
    15         try:
    16             return getattr(self.__local, self.__name__)
    17 返回return self.__local(),执行偏函数
    18 def _lookup_req_object(name):
    19     top = _request_ctx_stack.top
    20     if top is None:
    21         raise RuntimeError(_request_ctx_err_msg)
    22     return getattr(top, name)
    23 4.从top = _request_ctx_stack.top中去拿开始存入的ctx对象,然后再从ctx对象中去拿request: return self._local.stack[-1]
    24 5.通过getattr(top, name)==getattr(ctx对象,request)
    25 
    26 如果是打印print(request.method)
    27 其实就是多了一步,再执行__str__方法之前执行def __getattr__(self, name)方法
    28 
    29 
    30 代码示例展示:
    31 from flask import Flask,request,session,g,current_app
    32 
    33 app = Flask(__name__)
    34 
    35 @app.route('/',methods=['GET',"POST"])
    36 def index():
    37     # request是 LocalProxy 的对象
    38     print(request)  # LocalProxy.__str__ --> str(LocalProxy._get_current_object) --> 调用偏函数 --> ctx.request
    39     request.method  # LocalProxy.__getattr__ -->
    40                                             # str(LocalProxy._get_current_object) --> 调用偏函数 --> ctx.request
    41                                             # getattr(self._get_current_object(), name)          --> ctx.request.method
    42 
    43     request.path   # ctx.request.path
    44 
    45     print(session) # LocalProxy.__str__ --> str(LocalProxy._get_current_object) --> 调用偏函数 --> ctx.session
    46 
    47 
    48     print(g) # 执行g对象的__str__
    49     return "index"
    50 
    51 
    52 if __name__ == '__main__':
    53     app.__call__
    54     app.wsgi_app
    55     app.wsgi_app
    56     app.request_class
    57     app.run()
    复制代码

    d.第三阶段

    复制代码
     1 # 寻找视图函数并执行,获取返回值
     2 response = self.full_dispatch_request()
     3 
     4 1.先执行before_first_request
     5 self.try_trigger_before_first_request_functions()
     6 
     7 2.触发request_started信号,request_started
     8 request_started.send(self)
     9 补充:信号:
    10 from flask import Flask, signals, render_template
    11 
    12 app = Flask(__name__)
    13 
    14 
    15 # 往信号中注册函数
    16 def func(*args, **kwargs):
    17     print('触发型号', args, kwargs)
    18 
    19 
    20 signals.request_started.connect(func)
    21 
    22 
    23 # 触发信号: signals.request_started.send()
    24 
    25 @app.before_first_request
    26 def before_first1(*args, **kwargs):
    27     pass
    28 
    29 
    30 @app.before_first_request
    31 def before_first2(*args, **kwargs):
    32     pass
    33 
    34 
    35 @app.before_request
    36 def before_first3(*args, **kwargs):
    37     pass
    38 
    39 
    40 @app.route('/', methods=['GET', "POST"])
    41 def index():
    42     print('视图')
    43     return render_template('index.html')
    44 
    45 
    46 if __name__ == '__main__':
    47     app.wsgi_app
    48     app.run()
    49 
    50 3.再执行def preprocess_request(self):中的before_request
    51 
    52 4.# 执行视图函数dispatch_request-->def render_template(template_name_or_list, **context):-->before_render_template.send(app, template=template, context=context)-->template_rendered.send(app, template=template, context=context)
    53 rv = self.dispatch_request()
    54 
    55 5. 执行self.finalize_request(rv)--> response = self.process_response(response)-->self.session_interface.save_session(self, ctx.session, response)
    56 
    57 6.执行after_request
    58 
    59 7.执行信号request_finished.send(self, response=response)
    复制代码

    e.第四阶段

    1 ctx.auto_pop,将请求信息从threadinglocal中清除
    2 1.ctx.auto_pop -->self.pop(exc)-->_request_ctx_stack.pop()
    3 
    4 2._request_ctx_stack = LocalStack()-->return stack.pop()

    6.应用上下文原理(跟请求上下文的原理几乎一致)

    复制代码
     1 通过app_ctx.push()将app_ctx对象存储到threadinglocal中,分配一块独有的内存空间,app_ctx对象中具有:app_ctx.app与app_ctx.g
     2 
     3 1.app_ctx.g是存储变量用的
     4 示例代码:
     5 from flask import Flask,request,g
     6 
     7 app = Flask(__name__)
     8 
     9 @app.before_request
    10 def before():
    11     g.permission_code_list = ['list','add']
    12 
    13 
    14 @app.route('/',methods=['GET',"POST"])
    15 def index():
    16     print(g.permission_code_list)
    17     return "index"
    18 
    19 
    20 if __name__ == '__main__':
    21     app.run()
    复制代码

    补充:

    a:离线脚本示例

    复制代码
     1 """
     2 需求:不用数据库连接池,显示数据库连接
     3 """
     4 class SQLHelper(object):
     5 
     6     def open(self):
     7         pass
     8 
     9     def fetch(self,sql):
    10         pass
    11 
    12     def close(self):
    13         pass
    14 
    15     def __enter__(self):
    16         self.open()
    17         return self
    18 
    19     def __exit__(self, exc_type, exc_val, exc_tb):
    20         self.close()
    21 
    22 
    23 # obj = SQLHelper()
    24 # obj.open()
    25 # obj.fetch('select ....')
    26 # obj.close()
    27 
    28 
    29 with SQLHelper() as obj: # 自动调用类中的__enter__方法, obj就是__enter__返回值
    30     obj.fetch('xxxx')
    31     # 当执行完毕后,自动调用类 __exit__ 方法
    复制代码
    复制代码
     1 from flask import Flask,current_app,globals,_app_ctx_stack
     2 
     3 app1 = Flask('app01')
     4 app1.debug = False # 用户/密码/邮箱
     5 # app_ctx = AppContext(self):
     6 # app_ctx.app
     7 # app_ctx.g
     8 
     9 app2 = Flask('app02')
    10 app2.debug = True # 用户/密码/邮箱
    11 # app_ctx = AppContext(self):
    12 # app_ctx.app
    13 # app_ctx.g
    14 
    15 
    16 
    17 with app1.app_context():# __enter__方法 -> push -> app_ctx添加到_app_ctx_stack.local
    18     # {<greenlet.greenlet object at 0x00000000036E2340>: {'stack': [<flask.ctx.AppContext object at 0x00000000037CA438>]}}
    19     print(_app_ctx_stack._local.__storage__)
    20     print(current_app.config['DEBUG'])
    21 
    22     with app2.app_context():
    23         # {<greenlet.greenlet object at 0x00000000036E2340>: {'stack': [<flask.ctx.AppContext object at 0x00000000037CA438> ]}}
    24         print(_app_ctx_stack._local.__storage__)
    25         print(current_app.config['DEBUG'])
    26 
    27     print(current_app.config['DEBUG'])
    复制代码

    7.总结

    复制代码
      1 1. 面向对象私有
      2         class Foo(object):
      3 
      4             def __init__(self):
      5                 self.name = 'alex'
      6                 self.__age = 18
      7 
      8             def get_age(self):
      9                 return self.__age
     10 
     11         obj = Foo()
     12         # print(obj.name)
     13         # print(obj.get_age())
     14         # 强制获取私有字段
     15         print(obj._Foo__age)
     16         
     17 2. 谈谈Flask上下文管理
     18         - 与django相比是两种不同的实现方式。
     19             - django/tornado是通过传参数形式
     20             - flask是通过上下文管理
     21             两种都可以实现,只不过试下方式不一样。
     22             - 上下文管理:
     23                 - threading.local/Local类,其中创建了一个字典{greelet做唯一标识:存数据} 保证数据隔离
     24                 - 请求进来:
     25                     - 请求相关所有数据封装到了RequestContext中。
     26                     - 再讲RequestContext对象添加到Local中(通过LocalStack将对象添加到Local对象中)
     27                 - 使用,调用request
     28                     - 调用此类方法 request.method、print(request)、request+xxx 会执行LocalProxy中对应的方法
     29                     - 函数
     30                     - 通过LocalStack去Local中获取值。
     31                 - 请求终止
     32                     - 通过LocalStack的pop方法 Local中将值异常。
     33 
     34 3.上下文
     35         a. 请求上下文
     36             - request
     37             - session 
     38         b. 应用上下文
     39             
     40         
     41         请求流程:
     42                 _request_ctx_stack.local = {
     43                     
     44                 }
     45                 
     46                 _app_ctx_stack.local = {
     47                     
     48                 }
     49         
     50         
     51             3.1. 请求到来 ,有人来访问
     52                 # 将请求相关的数据environ封装到了RequestContext对象中
     53                 # 再讲对象封装到local中(每个线程/每个协程独立空间存储)
     54                 # ctx.app # 当前APP的名称
     55                 # ctx.request # Request对象(封装请求相关东西)
     56                 # ctx.session # 空
     57                 _request_ctx_stack.local = {
     58                     唯一标识:{
     59                         "stack":[ctx, ]
     60                     },
     61                     唯一标识:{
     62                         "stack":[ctx, ]
     63                     },
     64                 }
     65                 
     66                 
     67                 # app_ctx = AppContext对象
     68                 # app_ctx.app
     69                 # app_ctx.g
     70                     
     71                 _app_ctx_stack.local = {
     72                     唯一标识:{
     73                         "stack":[app_ctx, ]
     74                     },
     75                     唯一标识:{
     76                         "stack":[app_ctx, ]
     77                     },
     78                 }
     79             
     80             3.2. 使用 
     81                     from flask import request,session,g,current_app
     82                     
     83                     print(request,session,g,current_app)
     84                     
     85                     都会执行相应LocalProxy对象的 __str__
     86                     
     87                     current_app = LocalProxy(_find_app)
     88                         request = LocalProxy(partial(_lookup_req_object, 'request'))
     89                         session = LocalProxy(partial(_lookup_req_object, 'session'))
     90                         
     91                         current_app = LocalProxy(_find_app)
     92                         g = LocalProxy(partial(_lookup_app_object, 'g'))
     93                     
     94             3.3. 终止,全部pop
     95     
     96             问题1:多线程是如何体现?
     97             问题2:flask的local中保存数据时,使用列表创建出来的栈。为什么用栈?
     98                    - 如果写web程序,web运行环境;栈中永远保存1条数据(可以不用栈)。
     99                    - 写脚本获取app信息时,可能存在app上下文嵌套关系。
    100                         from flask import Flask,current_app,globals,_app_ctx_stack
    101 
    102                         app1 = Flask('app01')
    103                         app1.debug = False # 用户/密码/邮箱
    104                         # app_ctx = AppContext(self):
    105                         # app_ctx.app
    106                         # app_ctx.g
    107 
    108                         app2 = Flask('app02')
    109                         app2.debug = True # 用户/密码/邮箱
    110                         # app_ctx = AppContext(self):
    111                         # app_ctx.app
    112                         # app_ctx.g
    113 
    114 
    115 
    116                         with app1.app_context():# __enter__方法 -> push -> app_ctx添加到_app_ctx_stack.local
    117                             # {<greenlet.greenlet object at 0x00000000036E2340>: {'stack': [<flask.ctx.AppContext object at 0x00000000037CA438>]}}
    118                             print(_app_ctx_stack._local.__storage__)
    119                             print(current_app.config['DEBUG'])
    120 
    121                             with app2.app_context():
    122                                 # {<greenlet.greenlet object at 0x00000000036E2340>: {'stack': [<flask.ctx.AppContext object at 0x00000000037CA438> ]}}
    123                                 print(_app_ctx_stack._local.__storage__)
    124                                 print(current_app.config['DEBUG'])
    125 
    126                             print(current_app.config['DEBUG'])
    127     
    128     3.3. 多app应用
    129     
    130             from werkzeug.wsgi import DispatcherMiddleware
    131             from werkzeug.serving import run_simple
    132             from flask import Flask, current_app
    133 
    134             app1 = Flask('app01')
    135 
    136             app2 = Flask('app02')
    137 
    138 
    139 
    140             @app1.route('/index')
    141             def index():
    142                 return "app01"
    143 
    144 
    145             @app2.route('/index2')
    146             def index2():
    147                 return "app2"
    148 
    149             # http://www.oldboyedu.com/index
    150             # http://www.oldboyedu.com/sec/index2
    151             dm = DispatcherMiddleware(app1, {
    152                 '/sec': app2,
    153             })
    154 
    155             if __name__ == "__main__":
    156                 run_simple('localhost', 5000, dm)
    157 
    158 4.信号    
    159         a. before_first_request
    160         b. 触发 request_started 信号
    161         c. before_request
    162         d. 模板渲染
    163             渲染前的信号 before_render_template.send(app, template=template, context=context)
    164                 rv = template.render(context) # 模板渲染
    165             渲染后的信号 template_rendered.send(app, template=template, context=context)
    166         e. after_request
    167         f. session.save_session()
    168         g. 触发 request_finished信号
    169         
    170         如果上述过程出错:
    171             触发错误处理信号 got_request_exception.send(self, exception=e)
    172             
    173         h. 触发信号 request_tearing_down
    174 
    175 5.面向对象
    176             - 封装 
    177                 class Foo:
    178                     def __init__(self):
    179                         self.age = 123
    180                         self.nmame = 'ssdf'
    181     
    182                 class Bar:
    183                     def __init__(self):
    184                         self.xx = 111
    185                         
    186                 
    187                 
    188                 class Base:
    189                     def __init__(self):
    190                         self.f = Foo()
    191                         self.x = Bar()
    192             - 某个值+括号
    193                 - 函数/方法
    194                 - 类
    195                 - 对象 
    196             
    197             - 特殊的双下划线方法:
    198                 __new__
    199                 __call__
    200                 __str__
    201                 __setattr__
    202                 __setitem__
    203                 __enter__
    204                 __exit__
    205                 __add__
    206                 
    207                 PS: Flask的LocalProxy中全部使用。
    208                 
    209             - 强制调用私有字段
    210                 - 派生类中无法调用基类私有字段
  • 相关阅读:
    零基础学python-2.24 一些常用函数
    零基础学python-2.23 模块
    零基础学python-2.22 回到我们的游戏 加入文件和异常
    tcp协议:三次握手四次挥手详解---总结
    centos7安装jmeter + ant
    centos7安装jenkins
    centos7安装tomcat
    centos7安装jdk (jdk-8u161-linux-x64.tar.gz 和 java-1.8.0-openjdk* 介绍)
    波浪场景jp@gc
    阶梯场景jp@gc
  • 原文地址:https://www.cnblogs.com/fengff/p/11953457.html
Copyright © 2020-2023  润新知