awesome Flask

  58 mins to read  

List [CTL]

    BBS部署于:http://bbs.iv4n.xyz,,项目地址

    接下来一点一点总结踩过的坑们:

    • flask_xxx,新版的Flask扩展把flask.ext.xxx改为了前面那个,我觉得算退步吧,一个ext包模块化结构不是清晰很多吗

    • flask_loginuser必须要有id属性,因为我用了MongoDB所以开始没赋id,之后看了文档解决

      class UserMixin(object):
        
          if not PY2:  # pragma: no cover
              # Python 3 implicitly set __hash__ to None if we override __eq__
              # We set it back to its default implementation
              __hash__ = object.__hash__
        
          @property
          def is_active(self):
              return True
        
          @property
          def is_authenticated(self):
              return True
        
          @property
          def is_anonymous(self):
              return False
        
          def get_id(self):
              try:
                  return text_type(self.id)
              except AttributeError:
                  raise NotImplementedError('No `id` attribute - override `get_id`')
      
    • Flask的蓝图,这个东西通俗点说就是让项目能更具模块化,比如说同类视图的实现放在同一个文件里,然后不同功能创建不同文件,但假如没有蓝图很难实现,因为涉及相互导入的问题,即a包导入b包,b包中又导入b包,而有了蓝图的话可以先实现功能,然后在项目的__init__.py中为app类注册实现功能蓝图

    • 相对包导入,这个写多自然就懂了。简单点说,Python3中使用from .. import xxx/from ..a import xxx这种叫做相对包导入,假如写函数库的话这样会为调用者省掉很多麻烦,但是!:一不小心报错。因为相对导入只有在包内可实现,而包必须在顶层文件夹内包含__init__.py,即使它为空。举例:

      /test
      |___ a.py(内含app实例)
      |___ /routes
      		|___ b.py(需导入app实例)
      

      我在b.py里写from ..a import app,报错ValueError: attempted relative import beyond top-level package因为test本身不是一个包,所以b.py无法相对导入上层文件夹里的变量,解决办法:在/test下创建__init__,然后将/test当做包,在/test同级目录下创建文件调用b.py。在Flask开发时,我在项目文件夹中的__init__.py中实例化app/mongo/bootstrap,然后在项目文件夹同级创建启动文件,启动文件中再为app注册蓝图。假如不在外层启动的话,__init__.py需要导入数据库蓝图注册,而数据库蓝图文件中又需要导入__init__.py来实例化数据库对象,这就造成了相互导入,不报错就怪了

      另外__init__.py中的变量是会被提到比包内文件高一层的位置上,即直接是包级文件,举个例子:

      /a
      |___ b.py(内含class t)
      |___ __init__.py
      

      这里假如我从外部导入类t的话需要from a.b import t,但假如我在__init__.py中写from .b import t,我再从外部导入就只需要from a import t,即把t提升到了包级

    • flask_login中的user.loader回调,文档中告诉你要实现函数:

      @login_manager.user_loader
      def load_user(userid):
          return get_user_obj(userid) if xxx else None
      

      即通过这个函数返回id对应的用户对象,想想flask_login的内部实现就可以理解,它在内部维护了一个登录用户的实例栈,通过每个用户唯一的id来获取用户实例,可以阅读源码(注释写的非常清楚了):

      def user_loader(self, callback):
              '''
              This sets the callback for reloading a user from the session. The
              function you set should take a user ID (a ``unicode``) and return a
              user object, or ``None`` if the user does not exist.
        
              :param callback: The callback for retrieving a user object.
              :type callback: callable
              '''
              self.user_callback = callback
              return callback
        
      def reload_user(self, user=None):
              '''
              This set the ctx.user with the user object loaded by your customized
              user_loader callback function, which should retrieved the user object
              with the user_id got from session.
        
              Syntax example:
              from flask_login import LoginManager
              @login_manager.user_loader
              def any_valid_func_name(user_id):
                  # get your user object using the given user_id,
                  # if you use SQLAlchemy, for example:
                  user_obj = User.query.get(int(user_id))
                  return user_obj
        
              Reason to let YOU define this self.user_callback:
                  Because we won't know how/where you will load you user object.
              '''
              ctx = _request_ctx_stack.top
        
              if user is None:
                  user_id = session.get('user_id')
                  if user_id is None:
                      ctx.user = self.anonymous_user()
                  else:
                      if self.user_callback is None:
                          raise Exception(
                              "No user_loader has been installed for this "
                              "LoginManager. Refer to"
                              "https://flask-login.readthedocs.io/"
                              "en/latest/#how-it-works for more info.")
                      user = self.user_callback(user_id)
                      if user is None:
                          ctx.user = self.anonymous_user()
                      else:
                          ctx.user = user
              else:
                  ctx.user = user
      

      这里因为我用的MongoDB,所以网上没有现成实现,几乎都是SqlArchemy的,这里我给用户类添加了一个类方法

      class BaseUser(UserMixin):
            
          __slots__ = ['id', 'uname', 'passwd', 'passwd_hash', 'email', 'role']
            
          def __init__(self, _id, uname=None, passwd=None, email=None, role="basic"):
              self.id=_id
              self.uname = uname
              self.passwd = passwd
              self.email = email
              self.role = role
              self.passwd_hash = hashlib.sha256(passwd.encode('utf-8')+(hashlib.md5(salt.encode("utf-8")).hexdigest()).encode("utf-8")).hexdigest()
            
      ## Other func
        
          @classmethod
          def query(cls, user_id):
              result = mongo.db.users.find_one({"_id": ObjectId(user_id)})
              return cls(result["_id"], result["uname"], result["passwd"], result["email"], result["role"])
      
    • MongoDB的数据库权限,MongoDB的未授权访问漏洞是因为未开启数据库auth,正确做法是先关闭auth然后添加数据库用户,添加root用户需到admin库,别的数据库管理员需到指定库,也就是说添加那个库权限就到哪个库执行createUser()(MongoDB 3.0以前是addUser),然后开启auth在开放到公网,不过一般问题也不大,别监听0.0.0.0就行了。此外,MongoDB的权限也很奇怪,dbAdmin居然没有读写数据库权限(一直说我无权限执行函数搞得我很迷惑),只有执行管理函数的权限,而读写权限需要readWrite,如下:

      /*
      * Read:允许用户读取指定数据库
      * readWrite:允许用户读写指定数据库
      * dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile
      * userAdmin:允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户
      * clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。
      * readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限
      * readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限
      * userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限
      * dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。
      * root:只在admin数据库中可用。超级账号,超级权限
      */
        
      > use flask
      > db.createUser({
            "user": "flask",
            "pwd": "flask",
            "roles": [
                "role": "readWrite",
                "db": "flask"
            ]
        })
      
    • 蓝图中的ulr_for函数,这个函数的前缀是需要实例化蓝图时的名字的,如

      main = Blueprint("a", __name__)
      url_for("a.xxx")
      
    • 头像,头像我没有保存到项目文件里然后open()啥的,我直接把图片二进制base64编码然后存进数据库了,或者其实不编码直接存也可以,因为MongoDB本身就是Bson格式存的数据,然后在后台创建了一个读取头像的API

    • sort分页,我这样写的get_posts函数,按页码select数据:

      def get_posts_api(limit=0, skip=0, **kwargs):
          for i in kwargs.keys():
              if type(i) != str or type(i) != int:
                  continue
              kwargs[i], _ = flask_real_escape(kwargs[i])  
          _posts = mongo.db.posts.find(kwargs).sort([("date", -1)]).skip(skip).limit(limit)
          posts = list(_posts)
          for i in posts:
              i["content"] = mistune.markdown(i["content"], escape=True, hard_wrap=True)
          return posts
      

      其中的sort函数查阅文档可知需要传进去一个元组列表,如sort([(a, 1), (b, -1)])

    • redirect函数,我服务器前加了一层Nginx反代,因为我总觉得Gunicorn是个玩具,丢在公网上实在不放心,就只让它监听本地让Nginx去请求它。Redirect会返回重定向的URL,然后set location响应头,Nginx配置需要加上proxy_set_header,不然redirect会直接返回127.0.0.1:2333/xxx到用户浏览器

      server {
              listen 80;
              server_name 39.105.187.104;
              location / {
                      proxy_pass http://127.0.0.1:2333;
                      proxy_redirect off;
                      proxy_set_header Host $host:$server_port;
              }
      }
      
    • Gunicorn的部署,直接gunicorn wsgi:app是不行的,提示找不到app对象,因为我的启动函数不在项目包里,而直接从包启动又没有注册蓝图。没办法看文档另一种方法创建工厂函数,改写gunicorn 'wsgi:create_app()',ok

    • 注册的验证码我用了一个随机四个数的运算,当然其实没有什么卵用,恶意爬虫直接爬下来eval就完事了,我可能就是好玩吧。最初我是在routes/auth.py中定义了全局变量的随机生成,本地测试一切正常(缓存的缘故),部署后就崩了,原因是第一次访问注册页生成一个验证码,然后post数据的时候算作第二次访问,这时候验证码已经刷新了,而用户提交的依旧是第一次访问生成在html里的验证码。解决办法将验证码保存到session全局变量中即可


    Flask && Werkzeug源码阅读

    Flask

    其实Flask框架整体最亮眼的是它的四个全局变量概念(session/current_app/request/g)以及上下文的概念,全局变量的实现是在WerkzeugLocal模块,所以会着重学习它的实现

    上下文的实现:

    class _AppCtxGlobals(object):
        """A plain object. Used as a namespace for storing data during an
        application context.
    
        Creating an app context automatically creates this object, which is
        made available as the :data:`g` proxy.
    
        .. describe:: 'key' in g
    
            Check whether an attribute is present.
    
            .. versionadded:: 0.10
    
        .. describe:: iter(g)
    
            Return an iterator over the attribute names.
    
            .. versionadded:: 0.10
        """
    
        def get(self, name, default=None):
            """Get an attribute by name, or a default value. Like
            :meth:`dict.get`.
    
            :param name: Name of attribute to get.
            :param default: Value to return if the attribute is not present.
    
            .. versionadded:: 0.10
            """
            return self.__dict__.get(name, default)
    
        def pop(self, name, default=_sentinel):
            """Get and remove an attribute by name. Like :meth:`dict.pop`.
    
            :param name: Name of attribute to pop.
            :param default: Value to return if the attribute is not present,
                instead of raise a ``KeyError``.
    
            .. versionadded:: 0.11
            """
            if default is _sentinel:
                return self.__dict__.pop(name)
            else:
                return self.__dict__.pop(name, default)
    
        def setdefault(self, name, default=None):
            """Get the value of an attribute if it is present, otherwise
            set and return a default value. Like :meth:`dict.setdefault`.
    
            :param name: Name of attribute to get.
            :param: default: Value to set and return if the attribute is not
                present.
    
            .. versionadded:: 0.11
            """
            return self.__dict__.setdefault(name, default)
    
        def __contains__(self, item):
            return item in self.__dict__
    
        def __iter__(self):
            return iter(self.__dict__)
    
        def __repr__(self):
            top = _app_ctx_stack.top
            if top is not None:
                return '<flask.g of %r>' % top.app.name
            return object.__repr__(self)
        
    
    class AppContext(object):
        """The application context binds an application object implicitly
        to the current thread or greenlet, similar to how the
        :class:`RequestContext` binds request information.  The application
        context is also implicitly created if a request context is created
        but the application is not on top of the individual application
        context.
        """
    
        def __init__(self, app):
            self.app = app
            self.url_adapter = app.create_url_adapter(None)
            self.g = app.app_ctx_globals_class()
    
            # Like request context, app contexts can be pushed multiple times
            # but there a basic "refcount" is enough to track them.
            self._refcnt = 0
    
        def push(self):
            """Binds the app context to the current context."""
            self._refcnt += 1
            if hasattr(sys, 'exc_clear'):
                sys.exc_clear()
            _app_ctx_stack.push(self)
            appcontext_pushed.send(self.app)
    
        def pop(self, exc=_sentinel):
            """Pops the app context."""
            try:
                self._refcnt -= 1
                if self._refcnt <= 0:
                    if exc is _sentinel:
                        exc = sys.exc_info()[1]
                    self.app.do_teardown_appcontext(exc)
            finally:
                rv = _app_ctx_stack.pop()
            assert rv is self, 'Popped wrong app context.  (%r instead of %r)' \
                % (rv, self)
            appcontext_popped.send(self.app)
    
        def __enter__(self):
            self.push()
            return self
    
        def __exit__(self, exc_type, exc_value, tb):
            self.pop(exc_value)
    
            if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
                reraise(exc_type, exc_value, tb)
    
    
    class RequestContext(object):
        """The request context contains all request relevant information.  It is
        created at the beginning of the request and pushed to the
        `_request_ctx_stack` and removed at the end of it.  It will create the
        URL adapter and request object for the WSGI environment provided.
    
        Do not attempt to use this class directly, instead use
        :meth:`~flask.Flask.test_request_context` and
        :meth:`~flask.Flask.request_context` to create this object.
    
        When the request context is popped, it will evaluate all the
        functions registered on the application for teardown execution
        (:meth:`~flask.Flask.teardown_request`).
    
        The request context is automatically popped at the end of the request
        for you.  In debug mode the request context is kept around if
        exceptions happen so that interactive debuggers have a chance to
        introspect the data.  With 0.4 this can also be forced for requests
        that did not fail and outside of ``DEBUG`` mode.  By setting
        ``'flask._preserve_context'`` to ``True`` on the WSGI environment the
        context will not pop itself at the end of the request.  This is used by
        the :meth:`~flask.Flask.test_client` for example to implement the
        deferred cleanup functionality.
    
        You might find this helpful for unittests where you need the
        information from the context local around for a little longer.  Make
        sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in
        that situation, otherwise your unittests will leak memory.
        """
    
        def __init__(self, app, environ, request=None):
            self.app = app
            if request is None:
                request = app.request_class(environ)
            self.request = request
            self.url_adapter = app.create_url_adapter(self.request)
            self.flashes = None
            self.session = None
    
            # Request contexts can be pushed multiple times and interleaved with
            # other request contexts.  Now only if the last level is popped we
            # get rid of them.  Additionally if an application context is missing
            # one is created implicitly so for each level we add this information
            self._implicit_app_ctx_stack = []
    
            # indicator if the context was preserved.  Next time another context
            # is pushed the preserved context is popped.
            self.preserved = False
    
            # remembers the exception for pop if there is one in case the context
            # preservation kicks in.
            self._preserved_exc = None
    
            # Functions that should be executed after the request on the response
            # object.  These will be called before the regular "after_request"
            # functions.
            self._after_request_functions = []
    
            self.match_request()
    
        def _get_g(self):
            return _app_ctx_stack.top.g
        def _set_g(self, value):
            _app_ctx_stack.top.g = value
        g = property(_get_g, _set_g)
        del _get_g, _set_g
    
        def copy(self):
            """Creates a copy of this request context with the same request object.
            This can be used to move a request context to a different greenlet.
            Because the actual request object is the same this cannot be used to
            move a request context to a different thread unless access to the
            request object is locked.
    
            .. versionadded:: 0.10
            """
            return self.__class__(self.app,
                environ=self.request.environ,
                request=self.request
            )
    
        def match_request(self):
            """Can be overridden by a subclass to hook into the matching
            of the request.
            """
            try:
                url_rule, self.request.view_args = \
                    self.url_adapter.match(return_rule=True)
                self.request.url_rule = url_rule
            except HTTPException as e:
                self.request.routing_exception = e
    
        def push(self):
            """Binds the request context to the current context."""
            # If an exception occurs in debug mode or if context preservation is
            # activated under exception situations exactly one context stays
            # on the stack.  The rationale is that you want to access that
            # information under debug situations.  However if someone forgets to
            # pop that context again we want to make sure that on the next push
            # it's invalidated, otherwise we run at risk that something leaks
            # memory.  This is usually only a problem in test suite since this
            # functionality is not active in production environments.
            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)
    
            # Open the session at the moment that the request context is available.
            # This allows a custom open_session method to use the request context.
            # Only open a new session if this is the first time the request was
            # pushed, otherwise stream_with_context loses the session.
            if self.session is None:
                session_interface = self.app.session_interface
                self.session = session_interface.open_session(
                    self.app, self.request
                )
    
                if self.session is None:
                    self.session = session_interface.make_null_session(self.app)
    
        def pop(self, exc=_sentinel):
            """Pops the request context and unbinds it by doing that.  This will
            also trigger the execution of functions registered by the
            :meth:`~flask.Flask.teardown_request` decorator.
    
            .. versionchanged:: 0.9
               Added the `exc` argument.
            """
            app_ctx = self._implicit_app_ctx_stack.pop()
    
            try:
                clear_request = False
                if not self._implicit_app_ctx_stack:
                    self.preserved = False
                    self._preserved_exc = None
                    if exc is _sentinel:
                        exc = sys.exc_info()[1]
                    self.app.do_teardown_request(exc)
    
                    # If this interpreter supports clearing the exception information
                    # we do that now.  This will only go into effect on Python 2.x,
                    # on 3.x it disappears automatically at the end of the exception
                    # stack.
                    if hasattr(sys, 'exc_clear'):
                        sys.exc_clear()
    
                    request_close = getattr(self.request, 'close', None)
                    if request_close is not None:
                        request_close()
                    clear_request = True
            finally:
                rv = _request_ctx_stack.pop()
    
                # get rid of circular dependencies at the end of the request
                # so that we don't require the GC to be active.
                if clear_request:
                    rv.request.environ['werkzeug.request'] = None
    
                # Get rid of the app as well if necessary.
                if app_ctx is not None:
                    app_ctx.pop(exc)
    
                assert rv is self, 'Popped wrong request context.  ' \
                    '(%r instead of %r)' % (rv, self)
    
        def auto_pop(self, exc):
            if self.request.environ.get('flask._preserve_context') or \
               (exc is not None and self.app.preserve_context_on_exception):
                self.preserved = True
                self._preserved_exc = exc
            else:
                self.pop(exc)
    
        def __enter__(self):
            self.push()
            return self
    
        def __exit__(self, exc_type, exc_value, tb):
            # do not pop the request stack if we are in debug mode and an
            # exception happened.  This will allow the debugger to still
            # access the request object in the interactive shell.  Furthermore
            # the context can be force kept alive for the test client.
            # See flask.testing for how this works.
            self.auto_pop(exc_value)
    
            if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
                reraise(exc_type, exc_value, tb)
    
        def __repr__(self):
            return '<%s \'%s\' [%s] of %s>' % (
                self.__class__.__name__,
                self.request.url,
                self.request.method,
                self.app.name,
            )
    

    因为它的底层实现是依赖Local类型,所以先看werkzeug中的实现


    Werkzeug

    Werkzeug是一个wsgi工具集,实现了web的常用功能,如request/response。它也是由Flask的作者开发的,Flask周边的Jinja、Werkzeug、flask_login都是由大佬一手开发orz

    前面提到的四种全局变量概念就是用它实现的,比如在定义一个路由函数时,我们不需要为函数传入request变量,而可以直接由全局变量request(其实是request的代理)获取到我们当前请求的数据

    # handle in Flask
    @app.route("/")
    def index():
        return "x"
    
    
    # handle in other web framework, such as Django
    def index(request):
        return HttpResponse("x")
    

    看一下Flask源码,这些全局变量是什么东西:

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

    _request_ctx_stack_app_ctx_stackLocalStack对象,衍生的四种全局变量都是对上下文对象某个成员的代理,接下来看werkzeug/local里的具体实现


    Local类

    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__()
            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)
    

    首先,__slots__限定了实例只有两个属性'__storage__', '__ident_func__'

    看到__init__函数中是采用了object.__setattr__(self, x, x)来赋值,而不是直接self.x = xxsetattr(self, x, x)原因是重写了类的__setattr__方法后,这里想要调用原生的赋值行为(绕过重写的魔术方法),所以使用了这样的写法

    初始化函数为对象创建了__storage__字典和__ident_func__方法,默认为threading.get_ident(获取当前线程id),当然,假如是greenlet之类的协程的话是可以修改__ident_func__,使其获取协程id来实现协程间数据隔离

    __call__魔术方法以实例自身为参数构造了一个LocalProxy对象,待会看到LP类的时候再说

    所以可看出Local对象实现了一个线程隔离的哈希表,它的核心是__storage__,结构是:

    {
        `[thread id 1]: int`: {key: value},
        `[thread id 2]: int`: {key: value},
    }
    

    在不同线程中向Local对象set一个相同的key值,它的存储也是隔离的

    __release_local__方法的作用是释放当前线程的所有数据,即直接pop当前线程id


    LocalStack类

    class LocalStack(object):
    
        """This class works similar to a :class:`Local` but keeps a stack
        of objects instead.  This is best explained with an example::
    
            >>> ls = LocalStack()
            >>> ls.push(42)
            >>> ls.top
            42
            >>> ls.push(23)
            >>> ls.top
            23
            >>> ls.pop()
            23
            >>> ls.top
            42
    
        They can be force released by using a :class:`LocalManager` or with
        the :func:`release_local` function but the correct way is to pop the
        item from the stack after using.  When the stack is empty it will
        no longer be bound to the current context (and as such released).
    
        By calling the stack without arguments it returns a proxy that resolves to
        the topmost item on the stack.
    
        .. versionadded:: 0.6.1
        """
    
        def __init__(self):
            self._local = Local()
    
        def __release_local__(self):
            self._local.__release_local__()
    
        def _get__ident_func__(self):
            return self._local.__ident_func__
    
        def _set__ident_func__(self, value):
            object.__setattr__(self._local, '__ident_func__', value)
        __ident_func__ = property(_get__ident_func__, _set__ident_func__)
        del _get__ident_func__, _set__ident_func__
    
        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
    

    LocalStack类其实就是将Local封装了一层,在Local__storage__哈希表里新增了{'stack': []}作栈

    首先为实例赋值了一个Local对象,接着将成员Local对象的__ident_func__property装饰为getter/setter,也就相当于将成员对象的属性提升到了自身,因为LocalStack类也需要接口来修改自身的__ident_func__来实现除线程外的数据安全。P.s. 这里少了个空行,排版看起来怪怪的,我刚开始还以为是个IndentError

    接着它的push/pop也就实现了一个栈(以list实现),即LocalStack.Local.__storage__[thread_id]["stack"] = [],注意这里push的赋值: self._local.stack = rv = [],因为list是可变容器,所以下面的append方法实际修改了两个变量(同一对象的引用),而pop方法中,假如pop当前值后栈为空,则会直接release当前线程id的kv。

    而这里需要留意的是,Local__getattr__方法,当字典的键不存在时抛出异常,而我们看到在push方法的第一次初始化时会getattr,但它却正常执行,原因在于,当reflet的getattr函数设置default参数时会recover抛出的AttributeError,见说明When a default argument is given, it is returned when the attribute doesn't exist; without it, an exception is raised in that case.


    LocalManager

    LocalManager的作用是将Local/LocalStack对象进行封装聚合,实现了修改get_ident以及增加中间件的接口,很简单的功能:

    class LocalManager(object):
    
        """Local objects cannot manage themselves. For that you need a local
        manager.  You can pass a local manager multiple locals or add them later
        by appending them to `manager.locals`.  Every time the manager cleans up,
        it will clean up all the data left in the locals for this context.
    
        The `ident_func` parameter can be added to override the default ident
        function for the wrapped locals.
    
        .. versionchanged:: 0.6.1
           Instead of a manager the :func:`release_local` function can be used
           as well.
    
        .. versionchanged:: 0.7
           `ident_func` was added.
        """
    
        def __init__(self, locals=None, ident_func=None):
            if locals is None:
                self.locals = []
            elif isinstance(locals, Local):
                self.locals = [locals]
            else:
                self.locals = list(locals)
            if ident_func is not None:
                self.ident_func = ident_func
                for local in self.locals:
                    object.__setattr__(local, '__ident_func__', ident_func)
            else:
                self.ident_func = get_ident
    
        def get_ident(self):
            """Return the context identifier the local objects use internally for
            this context.  You cannot override this method to change the behavior
            but use it to link other context local objects (such as SQLAlchemy's
            scoped sessions) to the Werkzeug locals.
    
            .. versionchanged:: 0.7
               You can pass a different ident function to the local manager that
               will then be propagated to all the locals passed to the
               constructor.
            """
            return self.ident_func()
    
        def cleanup(self):
            """Manually clean up the data in the locals for this context.  Call
            this at the end of the request or use `make_middleware()`.
            """
            for local in self.locals:
                release_local(local)
    
        def make_middleware(self, app):
            """Wrap a WSGI application so that cleaning up happens after
            request end.
            """
            def application(environ, start_response):
                return ClosingIterator(app(environ, start_response), self.cleanup)
            return application
    
        def middleware(self, func):
            """Like `make_middleware` but for decorating functions.
    
            Example usage::
    
                @manager.middleware
                def application(environ, start_response):
                    ...
    
            The difference to `make_middleware` is that the function passed
            will have all the arguments copied from the inner application
            (name, docstring, module).
            """
            return update_wrapper(self.make_middleware(func), func)
    
        def __repr__(self):
            return '<%s storages: %d>' % (
                self.__class__.__name__,
                len(self.locals)
            )
    

    LocalProxy

    前面Local类和LocalStack类的__call__魔术方法:

    # class Local
    def __call__(self, proxy):
        """Create a proxy for a name."""
        return LocalProxy(self, proxy)
    
    # class LocalStack
    def __call__(self):
        def _lookup():
            rv = self.top
            if rv is None:
                raise RuntimeError('object unbound')
                return rv
        return LocalProxy(_lookup)
    

    这里的LocalStack是通过闭包将自身实例包裹进一个lookup函数并返回

    LocalProxy的实现:

    @implements_bool
    class LocalProxy(object):
    
        """Acts as a proxy for a werkzeug local.  Forwards all operations to
        a proxied object.  The only operations not supported for forwarding
        are right handed operands and any kind of assignment.
    
        Example usage::
    
            from werkzeug.local import Local
            l = Local()
    
            # these are proxies
            request = l('request')
            user = l('user')
    
    
            from werkzeug.local import LocalStack
            _response_local = LocalStack()
    
            # this is a proxy
            response = _response_local()
    
        Whenever something is bound to l.user / l.request the proxy objects
        will forward all operations.  If no object is bound a :exc:`RuntimeError`
        will be raised.
    
        To create proxies to :class:`Local` or :class:`LocalStack` objects,
        call the object as shown above.  If you want to have a proxy to an
        object looked up by a function, you can (as of Werkzeug 0.6.1) pass
        a function to the :class:`LocalProxy` constructor::
    
            session = LocalProxy(lambda: get_current_request().session)
    
        .. versionchanged:: 0.6.1
           The class can be instantiated with a callable as well now.
        """
        __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 __repr__(self):
            try:
                obj = self._get_current_object()
            except RuntimeError:
                return '<%s unbound>' % self.__class__.__name__
            return repr(obj)
    
        def __bool__(self):
            try:
                return bool(self._get_current_object())
            except RuntimeError:
                return False
    
        def __unicode__(self):
            try:
                return unicode(self._get_current_object())  # noqa
            except RuntimeError:
                return repr(self)
    
        def __dir__(self):
            try:
                return dir(self._get_current_object())
            except RuntimeError:
                return []
    
        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]
    
        if PY2:
            __getslice__ = lambda x, i, j: x._get_current_object()[i:j]
    
            def __setslice__(self, i, j, seq):
                self._get_current_object()[i:j] = seq
    
            def __delslice__(self, i, j):
                del self._get_current_object()[i:j]
    
        __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
        __delattr__ = lambda x, n: delattr(x._get_current_object(), n)
        __str__ = lambda x: str(x._get_current_object())
        __lt__ = lambda x, o: x._get_current_object() < o
        __le__ = lambda x, o: x._get_current_object() <= o
        __eq__ = lambda x, o: x._get_current_object() == o
        __ne__ = lambda x, o: x._get_current_object() != o
        __gt__ = lambda x, o: x._get_current_object() > o
        __ge__ = lambda x, o: x._get_current_object() >= o
        __cmp__ = lambda x, o: cmp(x._get_current_object(), o)  # noqa
        __hash__ = lambda x: hash(x._get_current_object())
        __call__ = lambda x, *a, **kw: x._get_current_object()(*a, **kw)
        __len__ = lambda x: len(x._get_current_object())
        __getitem__ = lambda x, i: x._get_current_object()[i]
        __iter__ = lambda x: iter(x._get_current_object())
        __contains__ = lambda x, i: i in x._get_current_object()
        __add__ = lambda x, o: x._get_current_object() + o
        __sub__ = lambda x, o: x._get_current_object() - o
        __mul__ = lambda x, o: x._get_current_object() * o
        __floordiv__ = lambda x, o: x._get_current_object() // o
        __mod__ = lambda x, o: x._get_current_object() % o
        __divmod__ = lambda x, o: x._get_current_object().__divmod__(o)
        __pow__ = lambda x, o: x._get_current_object() ** o
        __lshift__ = lambda x, o: x._get_current_object() << o
        __rshift__ = lambda x, o: x._get_current_object() >> o
        __and__ = lambda x, o: x._get_current_object() & o
        __xor__ = lambda x, o: x._get_current_object() ^ o
        __or__ = lambda x, o: x._get_current_object() | o
        __div__ = lambda x, o: x._get_current_object().__div__(o)
        __truediv__ = lambda x, o: x._get_current_object().__truediv__(o)
        __neg__ = lambda x: -(x._get_current_object())
        __pos__ = lambda x: +(x._get_current_object())
        __abs__ = lambda x: abs(x._get_current_object())
        __invert__ = lambda x: ~(x._get_current_object())
        __complex__ = lambda x: complex(x._get_current_object())
        __int__ = lambda x: int(x._get_current_object())
        __long__ = lambda x: long(x._get_current_object())  # noqa
        __float__ = lambda x: float(x._get_current_object())
        __oct__ = lambda x: oct(x._get_current_object())
        __hex__ = lambda x: hex(x._get_current_object())
        __index__ = lambda x: x._get_current_object().__index__()
        __coerce__ = lambda x, o: x._get_current_object().__coerce__(x, o)
        __enter__ = lambda x: x._get_current_object().__enter__()
        __exit__ = lambda x, *a, **kw: x._get_current_object().__exit__(*a, **kw)
        __radd__ = lambda x, o: o + x._get_current_object()
        __rsub__ = lambda x, o: o - x._get_current_object()
        __rmul__ = lambda x, o: o * x._get_current_object()
        __rdiv__ = lambda x, o: o / x._get_current_object()
        if PY2:
            __rtruediv__ = lambda x, o: x._get_current_object().__rtruediv__(o)
        else:
            __rtruediv__ = __rdiv__
        __rfloordiv__ = lambda x, o: o // x._get_current_object()
        __rmod__ = lambda x, o: o % x._get_current_object()
        __rdivmod__ = lambda x, o: x._get_current_object().__rdivmod__(o)
        __copy__ = lambda x: copy.copy(x._get_current_object())
        __deepcopy__ = lambda x, memo: copy.deepcopy(x._get_current_object(), memo)
    

    首先,可以知道实例属性被限制了,而这里可以看到在__slots__中又添加了__dict__属性,看起来是矛盾的,也就是说其实还是能随意为实例新增属性的。但其实这里的目的是为了能让Proxy对象转发被代理obj的__dict__属性,所以用property装饰器将方法转为属性,然后在__slots__里添加了__dict__

    __slots__ = ('__local', '__dict__', '__name__', '__wrapped__')
    
    @property
    def __dict__(self):
        try:
            return self._get_current_object().__dict__
        except RuntimeError:
            raise AttributeError('__dict__')
    

    初始化函数:

    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)
    

    必填参数local,赋值给_LocalProxy__local,也就是__local,私有变量的类外名称会转化为_classname__funcname,因为调用的基类的__setatr__方法,所以需要用它的类外名称。接着判断local参数是否存在__call__魔术方法且无__release_local__,也就是说这里Local实例不会被赋值给__wrapped__,只有通过call LocalStack或自建的函数会被添加装饰标记

    代理的核心——_get_current_obj

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

    首先判断是否为普通lookup函数,如果是则直接调用返回。如是Local实例,则通过实例重写后的__getattr__获取name对应的值

    总结它的行为:如果是LocalStack实例化的代理,则返回顶部元素;如果是Local实例化的,则返回__storage__字典里实例化时__name__对应的变量。也就是说,代理只代理一个对象(或栈的top)

    剩下的部分就是重写了几乎所有的魔术方法,将它完全变成了一个代理对象,所有的操作符/运算符都是针对它所代理的local元素

    代理的意义:

    为什么需要在Local/LocalStack前加一层代理?其实这是设计模式里的代理模式

    假如说我们写这样的代码:

    s = LocalStack()
    s.push(obj1)
    s.push(obj2)
    
    def get_obj():
        return s.pop()
    
    # 1
    p = LocalProxy(get_obj)
    print(p)
    print(p)
    
    # 2
    p = get_obj()
    # or
    p = s.top
    

    第一种方法和第二种方法是不一样的,代理模式在每一次访问代理对象的时候都会动态获取,而不增代理的话,赋值后就无法再次动态获取了,当然用函数来获取也是可以的,但它就不太像操作对象的属性值了


    了解werkzeug自己实现的Local类后可以回来看Flask的上下文实现了

    _request_ctx_stack = LocalStack()
    _app_ctx_stack = LocalStack()
    

    请求上下文和程序上下文都是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'))
    

    四种全局变量构造函数的实参就是一个能获取前面两种栈的栈顶元素的函数:

    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
    

    这里分析一下RequestContextAPPContext和前者其实是差不多的

    初始化函数:

    def __init__(self, app, environ, request=None):
        self.app = app
        if request is None:
            request = app.request_class(environ)
        self.request = request
        self.url_adapter = app.create_url_adapter(self.request)
        self.flashes = None
        self.session = None
    
        # Request contexts can be pushed multiple times and interleaved with
        # other request contexts.  Now only if the last level is popped we
        # get rid of them.  Additionally if an application context is missing
        # one is created implicitly so for each level we add this information
        self._implicit_app_ctx_stack = []
    
        # indicator if the context was preserved.  Next time another context
        # is pushed the preserved context is popped.
        self.preserved = False
    
        # remembers the exception for pop if there is one in case the context
        # preservation kicks in.
        self._preserved_exc = None
    
        # Functions that should be executed after the request on the response
        # object.  These will be called before the regular "after_request"
        # functions.
        self._after_request_functions = []
    
        self.match_request()
    

    需要一个Flask app实例作实参,以及wsgienvironment,下面是g变量

    def _get_g(self):
        return _app_ctx_stack.top.g
    def _set_g(self, value):
        _app_ctx_stack.top.g = value
    g = property(_get_g, _set_g)
    del _get_g, _set_g
    

    property设置了ggetter/setterg是从app上下文栈的栈顶的上下文对象里取得的

    看一下AppContextg的定义:

    self.g = app.app_ctx_globals_class()
    

    进入Flask类看一看app_ctx_globals_class

    #: In Flask 0.9 this property was called `request_globals_class` but it
    #: was changed in 0.10 to :attr:`app_ctx_globals_class` because the
    #: flask.g object is now application context scoped.
    #:
    #: .. versionadded:: 0.10
    app_ctx_globals_class = _AppCtxGlobals
    

    可见它是一个类,回到flask/ctx查看类定义:

    class _AppCtxGlobals(object):
        """A plain object. Used as a namespace for storing data during an
        application context.
    
        Creating an app context automatically creates this object, which is
        made available as the :data:`g` proxy.
    
        .. describe:: 'key' in g
    
            Check whether an attribute is present.
    
            .. versionadded:: 0.10
    
        .. describe:: iter(g)
    
            Return an iterator over the attribute names.
    
            .. versionadded:: 0.10
        """
    
        def get(self, name, default=None):
            """Get an attribute by name, or a default value. Like
            :meth:`dict.get`.
    
            :param name: Name of attribute to get.
            :param default: Value to return if the attribute is not present.
    
            .. versionadded:: 0.10
            """
            return self.__dict__.get(name, default)
    
        def pop(self, name, default=_sentinel):
            """Get and remove an attribute by name. Like :meth:`dict.pop`.
    
            :param name: Name of attribute to pop.
            :param default: Value to return if the attribute is not present,
                instead of raise a ``KeyError``.
    
            .. versionadded:: 0.11
            """
            if default is _sentinel:
                return self.__dict__.pop(name)
            else:
                return self.__dict__.pop(name, default)
    
        def setdefault(self, name, default=None):
            """Get the value of an attribute if it is present, otherwise
            set and return a default value. Like :meth:`dict.setdefault`.
    
            :param name: Name of attribute to get.
            :param: default: Value to set and return if the attribute is not
                present.
    
            .. versionadded:: 0.11
            """
            return self.__dict__.setdefault(name, default)
    
        def __contains__(self, item):
            return item in self.__dict__
    
        def __iter__(self):
            return iter(self.__dict__)
    
        def __repr__(self):
            top = _app_ctx_stack.top
            if top is not None:
                return '<flask.g of %r>' % top.app.name
            return object.__repr__(self)
    

    g变量是通过__dict__设置实例属性来保存数据的

    从1.0开始,g变量存储在app上下文里,而不是请求上下文里,但它依然会在每一次请求重置,所以作用没有变:在一次请求间共享数据

    #: In Flask 0.9 this property was called `request_globals_class` but it
    #: was changed in 0.10 to :attr:`app_ctx_globals_class` because the
    #: flask.g object is now application context scoped.
    

    网上有人说之所以g只在一次请求间可共享,是因为每次请求会重置g变量,这是错误的,因为我们可以看到g的实例化是在AppContext的初始化函数中,而却没有留重置的接口,所以我们尝试打印每一次请求的app上下文id:

    from flask import Flask, _app_ctx_stack
    
    app = Flask(__name__)
    
    @app.route("/")
    def index():
        return str(id(_app_ctx_stack.top))
    
    @app.route("/s")
    def s():
        return str(id(_app_ctx_stack.top))
    
    app.run()
    
    

    运行即可知道,每一次请求的AppContext都是不同的对象,所以说,其实每一次的请求都创建了一个新的AppContext,也就是在app.wsgi_app中,由RequestContext来隐式实例化AppContext并压栈,而当一次请求结束时,app上下文栈和请求上下文栈都会被pop,这里在最后再分析

    继续看请求上下文push方法:

    def push(self):
        """Binds the request context to the current context."""
        # If an exception occurs in debug mode or if context preservation is
        # activated under exception situations exactly one context stays
        # on the stack.  The rationale is that you want to access that
        # information under debug situations.  However if someone forgets to
        # pop that context again we want to make sure that on the next push
        # it's invalidated, otherwise we run at risk that something leaks
        # memory.  This is usually only a problem in test suite since this
        # functionality is not active in production environments.
        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)
    
        # Open the session at the moment that the request context is available.
        # This allows a custom open_session method to use the request context.
        # Only open a new session if this is the first time the request was
        # pushed, otherwise stream_with_context loses the session.
        if self.session is None:
            session_interface = self.app.session_interface
            self.session = session_interface.open_session(
                self.app, self.request
            )
    
            if self.session is None:
                self.session = session_interface.make_null_session(self.app)
    

    看到它会确认当前AppContext不为空,且栈顶上下文的app为自身app,接着将自身实例压入_request_ctx_stack

    pop方法里:

    def pop(self, exc=_sentinel):
        """Pops the request context and unbinds it by doing that.  This will
        also trigger the execution of functions registered by the
        :meth:`~flask.Flask.teardown_request` decorator.
    
        .. versionchanged:: 0.9
           Added the `exc` argument.
        """
        app_ctx = self._implicit_app_ctx_stack.pop()
    
        try:
            clear_request = False
            if not self._implicit_app_ctx_stack:
                self.preserved = False
                self._preserved_exc = None
                if exc is _sentinel:
                    exc = sys.exc_info()[1]
                self.app.do_teardown_request(exc)
    
                # If this interpreter supports clearing the exception information
                # we do that now.  This will only go into effect on Python 2.x,
                # on 3.x it disappears automatically at the end of the exception
                # stack.
                if hasattr(sys, 'exc_clear'):
                    sys.exc_clear()
    
                request_close = getattr(self.request, 'close', None)
                if request_close is not None:
                    request_close()
                clear_request = True
        finally:
            rv = _request_ctx_stack.pop()
    
            # get rid of circular dependencies at the end of the request
            # so that we don't require the GC to be active.
            if clear_request:
                rv.request.environ['werkzeug.request'] = None
    
            # Get rid of the app as well if necessary.
            if app_ctx is not None:
                app_ctx.pop(exc)
    
            assert rv is self, 'Popped wrong request context.  ' \
                '(%r instead of %r)' % (rv, self)
    

    pop当前上下文时,会先执行teardown_request里的函数,这是Flask@teardown_request装饰器注册的hook函数,最后断言pop出的请求上下文is自身实例

    查看flask/app的源码:

    def app_context(self):
        """Create an :class:`~flask.ctx.AppContext`. Use as a ``with``
        block to push the context, which will make :data:`current_app`
        point at this application.
    
        An application context is automatically pushed by
        :meth:`RequestContext.push() <flask.ctx.RequestContext.push>`
        when handling a request, and when running a CLI command. Use
        this to manually create a context outside of these situations.
    
        ::
    
            with app.app_context():
                init_db()
    
        See :doc:`/appcontext`.
    
        .. versionadded:: 0.9
        """
        return AppContext(self)
    
    def request_context(self, environ):
        """Create a :class:`~flask.ctx.RequestContext` representing a
        WSGI environment. Use a ``with`` block to push the context,
        which will make :data:`request` point at this request.
    
        See :doc:`/reqcontext`.
    
        Typically you should not call this from your own code. A request
        context is automatically pushed by the :meth:`wsgi_app` when
        handling a request. Use :meth:`test_request_context` to create
        an environment and context instead of this method.
    
        :param environ: a WSGI environment
        """
        return RequestContext(self, environ)
    
    

    在请求到来时,werkzeug调用Flask实例,接着创建一个请求上下文,而假如此时app栈为空,RequestContext的push方法则会隐式实例化一个AppContext并压栈

    def wsgi_app(self, environ, start_response):
        """The actual WSGI application. This is not implemented in
        :meth:`__call__` so that middlewares can be applied without
        losing a reference to the app object. Instead of doing this::
    
            app = MyMiddleware(app)
    
        It's a better idea to do this instead::
    
            app.wsgi_app = MyMiddleware(app.wsgi_app)
    
        Then you still have the original application object around and
        can continue to call methods on it.
    
        .. versionchanged:: 0.7
            Teardown events for the request and app contexts are called
            even if an unhandled error occurs. Other events may not be
            called depending on when an error occurs during dispatch.
            See :ref:`callbacks-and-errors`.
    
        :param environ: A WSGI environment.
        :param start_response: A callable accepting a status code,
            a list of headers, and an optional exception context to
            start the response.
        """
        ctx = self.request_context(environ)
        error = None
        try:
            try:
                ctx.push()
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                response = self.handle_exception(e)
            except:
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error)
    
    def __call__(self, environ, start_response):
        """The WSGI server calls the Flask application object as the
        WSGI application. This calls :meth:`wsgi_app` which can be
        wrapped to applying middleware."""
        return self.wsgi_app(environ, start_response)
    
    

    最后总结几点:

    • 一般情况下AppContext的生命周期同RequestContext,甚至准确点说它的生命周期比请求上下文还要短一点。它只是作一个获取当前应用的代理。是随请求到来时与请求上下文一起创建的,而并不是很多人所想的它是当前应用的上下文

      直观点验证,我们修改Flask的源码,分别在AppContextRequestContext的初始化函数里打印debug信息,并定义析构函数,在析构函数里也打印debug信息,运行后的结果:

       * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
      request ctx init
      app ctx init
      app ctx del
      request ctx del
      127.0.0.1 - - [20/Jan/2019 18:01:42] "GET / HTTP/1.1" 200 -
      request ctx init
      app ctx init
      app ctx del
      request ctx del
      127.0.0.1 - - [20/Jan/2019 18:02:42] "GET /s HTTP/1.1" 200 -
      

      可以清晰的看到它们的生命周期

    • 当请求到来时,由werkzeug调用Flask对象的__call__魔术方法,接着调用wsgi_app,创建一个请求上下文,接着将请求上下文push到请求上下文栈中,而它判断目前无应用上下文,就会隐式创建,并入栈,在请求结束后,会执行一系列hook函数,接着调用请求上下文的pop方法,此时会判断是否之前隐式创建了应用上下文,如是,则一起pop掉:

      # Request contexts can be pushed multiple times and interleaved with
      # other request contexts.  Now only if the last level is popped we
      # get rid of them.  Additionally if an application context is missing
      # one is created implicitly so for each level we add this information
      self._implicit_app_ctx_stack = []
        
      # ...
      def push(self):
          # ...
          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)
                
      def pop(self, exc=_sentinel):
          """Pops the request context and unbinds it by doing that.  This will
              also trigger the execution of functions registered by the
              :meth:`~flask.Flask.teardown_request` decorator.
        
              .. versionchanged:: 0.9
                 Added the `exc` argument.
              """
          app_ctx = self._implicit_app_ctx_stack.pop()
            
          # ...
          if app_ctx is not None:
              app_ctx.pop(exc)
      
    • 栈的意义,因为Local对象已经线程(协程)间数据隔离了,所以只需讨论单线程的情况。当单线程请求经历多个中间件时,AppContext一层一层压栈,能保证获取到的总是目前处理的app;而对于请求上下文,在写应用时不会有这个情况,之所以用栈结构是为了在写测试或离线脚本时手动with app.request_context()时能数据隔离