zoukankan      html  css  js  c++  java
  • Flask

    参考

    1. http://flask.pocoo.org/docs/1.0/advanced_foreword/#thread-locals-in-flask
    2. https://zhuanlan.zhihu.com/p/33732859
    3. https://www.zhihu.com/question/25033592/answer/34449852
    4. https://www.zhihu.com/question/269905592/answer/364928400
    5. http://flask.pocoo.org/docs/1.0/appcontext/#purpose-of-the-context
    6. http://flask.pocoo.org/docs/1.0/reqcontext/
    7. http://flask.pocoo.org/docs/1.0/appcontext/#storing-data
      其实看5和6,7就够了,因为是权威的文档

    Flask的上下文管理很重要,留坑

    1-5为预备知识,从6正式开始

    现象:

    Flask 从客户端收到请求时,要让视图函数能访问一些对象,这样才能处理请求。请求对象就是一个很好的例子,它封装了客户端发送的HTTP 请求。要想让视图函数能够访问请求对象,一个显而易见的方式是将其作为参数传入视图函数,不过这会导致程序中的每个视图函数都增加一个参数。除了访问请求对象,如果视图函数在处理请求时还要访问其他对象,情况会变得更糟。为了避免大量可有可无的参数把视图函数弄得一团糟,Flask 使用上下文临时把某些对象变为全局可访问

    PS: 访问其他对象,例如请求对象

    django/tornado是通过传参数形式,Flask是通过上下文管理。

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

    虽然threading.local与这个实现没关系,不过要先了解threading.local对象才能理解Flask的上下文管

    1. threading.local对象,用于为每个线程开辟一块空间来保存它独有的值。

    Flask的上下文管理借助了这个思想。Thread-local data is data whose values are thread specific
    本地线程,保证即使是多个线程,自己的值也是互相隔离。

    import threading
    
    
    # class Foo(object):
    #     def __init__(self):
    #         self.name = 0
    #
    # local_values = Foo()
    
    local_values = threading.local()
    
    
    def func(num):
        local_values.name = num
        import time
        time.sleep(1)
        print(local_values.name, threading.current_thread().name)
    
    
    for i in range(20):
        th = threading.Thread(target=func, args=(i,), name='线程%s' % i)
        th.start()
    

    如果不用threading.local(),那么

    因为修改的值始终保存在同一个对象里面,sleep一秒后,全部线程执行完毕,值变成了最后的值19。

    2. 单进程单线程多协程中,threading.local会出问题,因为协程数据都存在同一个线程里。

    解决方法为:
    单进程单线程多协程中,程序不再支持协程,就可以使用threading.local对象。
    单进程单线程多协程中,程序想支持协程,那么自定义类似threading.local对象。如下所示

    
    > 自定义类似threading.local对象,支持协程(Python中greenlet就是协程),原理是一个标识identification对应一个字典(存储数据的地方)
    """
    {
       identification:{k:v},一个标识对应一个字典,threading.local工作原理类似这样,看源码可知,
        class _localimpl:
        """A class managing thread-local dicts"""
    }
    
    
    
    """
    import threading
    try:
        #获取标识identification
        from greenlet import getcurrent as get_ident # 协程
    except ImportError:
        try:
            from thread import get_ident
        except ImportError:
            from _thread import get_ident # 线程
    
    # 模拟threading.Local()且支持协程
    class Local(object):
        def __init__(self):
            self.storage = {}
            self.get_ident = get_ident
    
        def set(self,k,v):
            ident = self.get_ident()
            origin = self.storage.get(ident)
            if not origin:
                origin = {k:v}
            else:
                origin[k] = v
            self.storage[ident] = origin
    
        def get(self,k):
            ident = self.get_ident()
            origin = self.storage.get(ident)
            if not origin:
                return None
            return origin.get(k,None)
    
    local_values = Local()
    
    
    def task(num):
        local_values.set('name',num)
        import time
        time.sleep(1)
        print(local_values.get('name'), threading.current_thread().name)
    
    
    for i in range(20):
        th = threading.Thread(target=task, args=(i,),name='线程%s' % i)
        th.start()
    

    3. 注意__setattr__的坑,为往后的点作铺垫.

    http://www.cnblogs.com/allen2333/p/9019660.html
    改正后,可以用__setattr__,getattr

    import threading
    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):
    
        def __init__(self):
            object.__setattr__(self, '__storage__', {})
            object.__setattr__(self, '__ident_func__', get_ident)
    
    
        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_values = Local()
    
    
    def task(num):
        local_values.name = num
        import time
        time.sleep(1)
        print(local_values.name, threading.current_thread().name)
    
    
    for i in range(20):
        th = threading.Thread(target=task, args=(i,),name='线程%s' % i)
        th.start()
    

    4. 上下文原理

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from functools import partial
    from flask.globals import LocalStack, LocalProxy
     
    ls = LocalStack()
     
     
    class RequestContext(object):
        def __init__(self, environ):
            self.request = environ
     
     
    def _lookup_req_object(name):
        top = ls.top
        if top is None:
            raise RuntimeError(ls)
        return getattr(top, name)
     
     
    session = LocalProxy(partial(_lookup_req_object, 'request'))
     
    ls.push(RequestContext('c1')) # 当请求进来时,放入
    print(session) # 视图函数使用
    print(session) # 视图函数使用
    ls.pop() # 请求结束pop
     
     
    ls.push(RequestContext('c2'))
    print(session)
     
    ls.push(RequestContext('c3'))
    print(session)
    

    5. Flask内部实现

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
     
    from greenlet import getcurrent as get_ident
     
     
    def release_local(local):
        local.__release_local__()
     
     
    class Local(object):
        __slots__ = ('__storage__', '__ident_func__')
     
        def __init__(self):
            # self.__storage__ = {}
            # self.__ident_func__ = get_ident
            object.__setattr__(self, '__storage__', {})
            object.__setattr__(self, '__ident_func__', get_ident)
     
        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__()
            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)
     
     
    class LocalStack(object):
        def __init__(self):
            self._local = Local()
     
        def __release_local__(self):
            self._local.__release_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)
                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
     
     
    stc = LocalStack()
     
    stc.push(123)
    v = stc.pop()
     
    print(v)
    

    6. 大概流程(重要,去看源码理解)

    6.1 借鉴threading.Local(线程临时存对象的地方),Flask自定义Local对象(Local, LocalStack, LocalProxy)。Local类似threading.Local,是临时存对象的地方,比后者多出协程支持。

    上下文管理:
    类似threading.local ,Flask自己实现 Local类(临时存对象的地方),其中创建了一个字典,{用greenlet协程做唯一标识:存数据} 保证数据隔离
    请求进来时:

    • 请求相关所有数据封装到了RequestContext中。ctx = 封装RequestContext(request,session)
    • 再将ctx = RequestContext对象添加到Local中(通过LocalStack将对象添加到Local对象中)
      执行view function时,调用request:
      -调用此类方法: request.method、print(request)、request + xxx。
    from flask import request中查看request源码
    _request_ctx_stack = LocalStack()
    request = LocalProxy(partial(_lookup_req_object, 'request'))
    
    • request.method会执行LocalProxy中对应的方法(getattr) --> 对应的方法执行_get_current_object --> 偏函数(._lookup_req_object(), request)--> 通过LocalStack从Local中 --> 获取top = RequestContext --> ctx.request --> getattr --> ctx.request.method
      请求终止时:
    • ctx.auto_pop()
    • 通过LocalStack的pop方法,ctx从Local对象中移除。

    session

    • 过程一样,只是最后是ctx.session,相比较于ctx.request

    7. 应用上下文和请求上下文什么关系?

    from flask import Flask, request, g, globals
    app = Flask('_name_'), app._call_, ctx.push()
    在这两段代码进去看源码

    globals.py

    _request_ctx_stack = LocalStack()
    _app_ctx_stack = LocalStack()
    

    在app.__call__进去,ctx = self.request_context(environ),再从ctx.push()进去

    top = _request_ctx_stack.top
    
    ...
            top = _request_ctx_stack.top
            if top is not None and top.preserved:
                top.pop(top._preserved_exc)
    
            # Before we push the request context we have to ensure that there
            # is an application context.
            app_ctx = _app_ctx_stack.top
            if app_ctx is None or app_ctx.app != self.app:
                app_ctx = self.app.app_context()
                app_ctx.push()
                self._implicit_app_ctx_stack.append(app_ctx)
            else:
                self._implicit_app_ctx_stack.append(None)
    
            if hasattr(sys, 'exc_clear'):
                sys.exc_clear()
    
            _request_ctx_stack.push(self)
    

    所以应用上下文在每个请求上下文被push到__request_ctx_stack之前,自己被push到_app_ctx_stack中

    8. request和g的区别

    g,每个请求周期都会创建一个用于在请求周期中传递值的一个容器。
    request(在请求上下文中,LocalStack -- > 存储在Local中)和g都是临时容器,用来存储stuff。但是在程序中为了不修改request(封装了请求的参数),所以用g(在应用上下文中初始化,LocalStack -- >存储在Local中)来代替request存储数据、对象。
    _request_ctx_stack = LocalStack(),_app_ctx_stack = LocalStack(),两个是不同的LocalStack --> 不同的Local。PS:Local是临时存储stuff的地方。

    from flask import Flask, request, g, globals
    app = Flask('name'), app.call, ctx.push()
    在这两段代码进去看源码

    _request_ctx_stack.Local() = {
        唯一标识ident: {
            "stack":[request_ctx, ]
        }
    }
    
    _app_ctx_stack.Local() = {
        唯一标识ident: {
            "stack":[app_ctx, ]
        }
    }
    
    

    request_ctx = RequestContext(),app_ctx = AppContext()

    9. from flask import request,session,g,current_app

    参考6,print(request,session,g,current_app),都会执行相应LocalProxy对象的 str,也就是都是从LocalProxy出发
    唯一不同的是传递的偏函数的参数不一样

    
    request = LocalProxy(partial(_lookup_req_object, 'request'))
    session = LocalProxy(partial(_lookup_req_object, 'session'))
    current_app = LocalProxy(_find_app)
    g = LocalProxy(partial(_lookup_app_object, 'g'))
    
    

    10. 一个程序多线程有多少个Local? Web访问多app应用时,上下文管理是如何实现?

    还是原来的流程,wsgi --> push到Local。都是只有两个Local,通过唯一标识存储context.

    _request_ctx_stack.Local() = {
        唯一标识ident: {
            "stack":[request_ctx, ]
        }
    
        唯一标识ident: {
            "stack":[request_ctx, ]
        }
    
    ....
       
    }
    
    _app_ctx_stack.Local() = {
        唯一标识ident: {
            "stack":[app_ctx, ]
        }
    
        唯一标识ident: {
            "stack":[app_ctx, ]
        }
    }
    
    

    11. Flask的Local中保存数据时,使用列表创建出来的栈。为什么用栈?

    "stack":[app_ctx, ]
    

    如果写web程序,web运行环境;栈中永远保存1条数据(可以不用栈)。
    写脚本获取app信息时,可能存在app上下文嵌套关系,栈可能有多条数据。
    意味着栈是备用的!
    例如写测试脚本时,获取app1, app2的信息,测试一下数据库是否正确连接
    总结来说,为了部落!(多应用)

    from flask import Flask, current_app, globals, _app_ctx_stack
    
    app1 = Flask('app01')
    app1.debug = False  # 用户/密码/邮箱
    # app_ctx = AppContext(self):
    # app_ctx.app
    # app_ctx.g
    
    #RuntimeError: Working outside of application context.
    #print(current_app.config['DEBUG'])
    
    app2 = Flask('app02')
    app2.debug = True  # 用户/密码/邮箱
    # app_ctx = AppContext(self):
    # app_ctx.app
    # app_ctx.g
    
    # 写脚本的时候有上下文嵌套的写法。但是写网站的时候没可能有两个或以上app嵌套,堆栈里的stack永远放的是一个。"stack":[app_ctx, ]
    with app1.app_context():  # __enter__方法 -> push -> app_ctx添加到_app_ctx_stack.local
        # {<greenlet.greenlet object at 0x00000000036E2340>: {'stack': [<flask.ctx.AppContext object at 0x00000000037CA438>]}}
        print(_app_ctx_stack._local.__storage__)
        print(current_app)
        print(current_app.config['DEBUG'])
    
        # 写在里面,Local:{'stack' : [ctx1, ctx2]}有两个了!stack.top --> stack[-1] --> 还是拿栈顶的!
        with app2.app_context():
            # {<greenlet.greenlet object at 0x00000000036E2340>: {'stack': [<flask.ctx.AppContext object at 0x00000000037CA438> ]}}
            print(_app_ctx_stack._local.__storage__)
            print(current_app)
            print(current_app.config['DEBUG'])
    
    """    
    with app2.app_context():
        # {<greenlet.greenlet object at 0x00000000036E2340>: {'stack': [<flask.ctx.AppContext object at 0x00000000037CA438> ]}}
        print(_app_ctx_stack._local.__storage__)
        print(current_app)
        print(current_app.config['DEBUG'])
    """
    
    
    

  • 相关阅读:
    Docker学习笔记07_网络配置
    Docker学习笔记06_部署appache+tomcat+redis+mongo+python
    Docker学习笔记05_部署nginx+php+mysql+phpmyadmin
    Docker学习笔记04_镜像管理
    Docker学习笔记03_容器的简单应用
    Docker学习笔记02_基本操作
    Docker学习笔记01_CentOS 7安装Docker
    Cisco Ironport ESA配置拒收黑名单
    CentOS 7安装Cobra
    jvm内存模型、常见参数及调优
  • 原文地址:https://www.cnblogs.com/allen2333/p/9019367.html
Copyright © 2011-2022 走看看