flask中的上下文_请求上下文和应用上下文

本文深入解析Flask框架的上下文管理机制,包括请求上下文和应用上下文的运作原理,展示了如何通过这一机制实现数据的即用即取,特别关注了request和session对象的生命周期管理。

前引

  在了解flask上下文管理机制之前,先来一波必知必会的知识点。

面向对象双下方法

  首先,先来聊一聊面向对象中的一些特殊的双下划线方法,比如__call__、__getattr__系列、__getitem__系列。

  __call__

  这个方法相信大家并不陌生,在单例模式中,我们可能用到过,除此之外,还想就没有在什么特殊场景中用到了。我们往往忽视了它一个很特殊的用法:对象object+()或者类Foo()+()这种很特殊的用法。在Flask上下文管理中,入口就是使用了这种方式。

  __getitem__系列

  使用这个系列的方法时,我们最大的印象就是调用对象的属性可以像字典取值一样使用中括号([])。使用中括号对对象中的属性进行取值、赋值或者删除时,会自动触发对应的__getitem__、__setitem__、__delitem__方法。

class Foo(object):

    def __init__(self):
        self.name = "boo"

    def __getitem__(self, item):
        print("调用__getitem__了")
        if item in self.__dict__:
            return self.__dict__[item]

    def __setitem__(self, key, value):
        print("调用__setitem__方法了")
        self.__dict__[key] = value

    def __delitem__(self, key):
        print("调用__delitem__")
        del self.__dict__[key]


foo = Foo()
ret = foo["name"]
# print(ret)     # 输出     调用__getitem__了      boo
foo["age"] = 18
# print(foo["age"])   # 输出   调用__setitem__方法了   调用__getitem__了    18
del foo["age"]   #  输出  调用__delitem__

__item__系列
item

 

  __getattr__系列

  使用对象取值、赋值或者删除时,会默认的调用对应的__getattr__、__setattr__、__delattr__方法。

  对象取值时,取值的顺序为:先从__getattribute__中找,第二步从对象的属性中找,第三步从当前类中找,第四步从父类中找,第五步从__getattr__中找,如果没有,直接抛出异常。

class Foo(object):

    def __init__(self):
        self.name = "boo"

    def __getattr__(self, item):
        print("调用__getattr__了")

    def __setattr__(self, key, value):
        print("调用__setattr__方法了")

    def __delattr__(self, item):
        print("调用__delattr__")


foo = Foo()
ret = foo.xxx    # 输出     调用__getattr__了
foo.age = 18    # 调用__setattr__方法了
del foo.age   #  输出  调用__delattr__

__attr__系列
attr

偏函数  

  再来说说Python中的偏函数

  python中有一个小工具包functools,这个包中包含了几个在很有用的小功能,比如:wraps:在使用装饰器时,使用这个方法可以保护函数的元信息。reduce:一个合并序列项为一个单一值的小方法。还有一个就是偏函数: partial  

  一句话来总结partial的作用,固定函数中的一些参数,返回一个新的函数,方便调用

from functools import partial

class Foo(object):

    def __init__(self):
        self.request = "request"
        self.session = "session"

foo = Foo()

def func(args):
    return getattr(foo,args)

re_func = partial(func,'request')
se_func = partial(func,'session')

print(re_func())

来一个复杂点的偏函数的用法
偏函数实例

仿照flask来实现一个更复杂的

from functools import partial


class HttpRequest(object):

    def __init__(self):
        self.method = "GET"
        self.body = b"name=abc@age=123"


class Foo(object):

    def __init__(self):
        self.request = HttpRequest()
        self.session = {"login":True,"is_super":False}

foo = Foo()

def func(args):
    return getattr(foo,args)

re_func = partial(func,'request')
se_func = partial(func,'session')


class LocalProxy(object):

    def __init__(self,local):
        self._local = local

    def _get_current_object(self):
        return self._local()

    def __getitem__(self, item):
        return getattr(self._get_current_object(),item)

request = LocalProxy(re_func)
ret = request._get_current_object().method
print(ret)

ret = request['method']
print(ret)

session = LocalProxy(se_func)
print(session._get_current_object())

仿flask版
仿flask

threading.local

  再来说一说threading.local方法

  在多线程中,同一个进程中的多个线程是共享一个内存地址的,多个线程操作数据时,就会造成数据的不安全,所以我们就要加锁。但是,对于一些变量,如果仅仅只在本线程中使用,怎么办?

  方法一,可以通过全局的字典,key为当前线程的线程ID,value为具体的值。

  方法二,使用threading.local方法

threading.local 在多线程操作时,为每一个线程创建一个值,使得线程之间各自操作自己 的值,互不影响。

import time
import threading

local = threading.local()

def func(n):
    local.val = n
    time.sleep(5)
    print(n)

for i in range(10):
    t = threading.Thread(target=func,args=(i,))
    t.start()

# 结果输出    0--9

threading.local示例
threading.local示例

 自定义使用threading.local的功能

import time
import threading
# from threading import current_thread as getcurrent
from greenlet import getcurrent

class Local(object):

    def __init__(self):
        object.__setattr__(self,"_storage",{})

    def __setattr__(self, key, value):

        # ident = threading.get_ident()
        ident = getcurrent()   # 定制粒度更细的
        if ident in self._storage:
            self._storage[ident][key] = value
        else:
            self._storage[ident] = {key:value}

    def __getattr__(self, item):
        # ident = threading.get_ident()
        ident = getcurrent()
        return self._storage[ident][item]

local = Local()

def func(n):
    local.val = n
    time.sleep(2)
    print(local.val)

for i in range(10):
    t = threading.Thread(target=func,args=(i,))
    t.start()

l
自定制更细粒度的threadingloca

仿照flask用栈来实现自定义threading.local的存取

from greenlet import getcurrent

class Local(object):

    def __init__(self):
        object.__setattr__(self,"_storage",{})

    def __setattr__(self, key, value):

        # ident = threading.get_ident()
        ident = getcurrent()   # 定制粒度更细的
        if ident in self._storage:
            self._storage[ident][key] = value
        else:
            self._storage[ident] = {key:value}

    def __getattr__(self, item):
        # ident = threading.get_ident()
        ident = getcurrent()
        return self._storage[ident][item]


class LocalStack(object):

    def __init__(self):
        self.local = Local()

    def push(self,item):
        self.local.stack = []
        self.local.stack.append(item)

    def pop(self):
        return self.local.stack.pop()

    def top(self):
        return self.local.stack[-1]

_local_stack = LocalStack()
_local_stack.push(55)
print(_local_stack.top())  # 取栈顶元素

利用栈
仿照flask用栈来实现自定义threading.local的存取

  预热完毕,来一波真正的操作,不过在正戏上演之前,先来提一嘴,flask与其他python框架比如(Django、tornado)在整个请求生命周期中对于数据的管理机制的不同。django、tornado是通过传参的形式传递数据,而flask是通过其特有的上下文管理机制来管理数据的。

  下面进入我们今天的正题----flask的上下文管理机制。在flask中,上下文管理机制分为两个大的部分:请求上下文和应用上下文。

flask的上下文管理机制

  接下来,从以下三个大的方面分别探讨flask的两大上下文管理机制。

  1.  方面一:请求进来时
  2.  方面二:视图函数
  3.  方面三:请求结束前

先来一个最简单的flask版的Hello World

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return "Hello World"

if __name__ == '__main__':
    app.run()

Flask版Hello World
Hello World

  启动一个flask项目时,会先执行app.run()方法,这是整个项目的入口,执行run方法时,接着执行werkzeug模块中的run_simple

  

  werkzeug中触发调用了Flask的__call__方法

 

 请求进来时

触发执行__call__方法,__call__方法的逻辑很简单,直接执行wsgi_app方法,将包含所有请求相关数据和一个响应函数传进去。

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return '<h1>Hello, web!</h1>'

上面的application()函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:

    environ:一个包含所有HTTP请求信息的dict对象;

    start_response:一个发送HTTP响应的函数。

在application()函数中,调用:

start_response('200 OK', [('Content-Type', 'text/html')])

符合wsgi协议标准的函数
符合wsgi协议标准的函数

  备注:__call__是一个符合wsgi标准的函数

执行wsgi_app方法

def wsgi_app(self, environ, start_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)

wsgi_app
wsgi_app

  第一步先执行了一个request_context的方法,将environ传进去,最后返回一个RequestContext类的对象,被ctx的变量接收(ctx=request_context(environ))

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)
request_context方法

这个ctx对象在初始化时,赋了两个非常有用的属性,一个是request,一个是session

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是一个Request()对象,这个对象就是我们在flask中使用的request对象,为我们提供了很多便捷的属性和方法,比如:request.method、request.form、request.args等等,另一个属性是session,初始为None。

  紧接着执行ctx.push()方法,这个方法中,在执行请求上下文对象ctx之前先实例化了一个app_context对象,先执行了app_context的push方法,然后才执行_request_ctx_stack对象中的top和_request_ctx_stack.push(self),最后对ctx中的session进行处理。

  所以,flask中的应用上下文发生在请求上下文之前

def push(self):           
       
        top = _request_ctx_stack.top
        if top is not None and top.preserved:
            top.pop(top._preserved_exc)
            
        # 在执行request_context请求上下文的push方法时,先执行了app_context应用上下文的push方法
        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()
            
        # 然后执行请求上下文对象中LocalStack对象的push方法
        _request_ctx_stack.push(self)

        # 最后处理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)
在ctx.push方法的执行逻辑  

  但是我们先说请求上下文,在处理完应用上下文的push方法后,紧接着执行了_request_ctx_stack对象的两个方法。

  而这个_request_ctx_stack是LocalStack这个类的对象。_request_ctx_stack = LocalStack()

   LocalStack有没有很眼熟,没错,flask内部使用的机制就是类似于我们上文中自定义的LocalStack的机制,实例化过程中使用了面向对象中的组合概念,self._local = Local(),然后在自身又实现了push、pop、top方法,这三个方法中都是通过反射从Local类的实例化对象中对一个stack属性进行append、pop、[-1]的操作,所以,Local对象中的stack属性对应的值一定是一个类似于列表的东西。通过对列表的操作,实现一个类似于栈的存取。

  接着聊聊这个Local类,在实例化时,会对每个对象生成一个storage的空字典。我们翻遍整个Local类的源码,发现内部并没有实现一个叫stack的方法或者属性,但是上面我们提到了LocalStack对象会对Local对象中的一个叫stack的东西进行一系列操作。找不到不会报错吗?

  这就是flask的巧妙之处,通过类的一些魔法方法巧妙的实现了相应的处理。在前引中,提到如果对象中没有某个属性,取值时,最终会执行类中的__getattr__方法,然后再做后续的异常处理,flask将所有的对应逻辑都实现在了类的__getattr__方法中,将每一个线程存储到字典中,在请求进来时,将每一个对应的请求ctx存在一个列表中,使用时直接调用,而不是通过传参的形式,更体现出了flask框架的轻量级。

  处理完_request_ctx_stack后,就该处理session了。

  在flask中,处理session时,非常的巧妙,完美的遵循了开闭原则,会先执行session_interface对象的open_session方法,在这个方法中,会先从用户请求的cookie中获取sessionid,获取该用户之前设置的session值,然后将值赋值到ctx.session中。

  处理完session后,ctx.push方法就执行完了,返回到最开始的app.wsgi_app方法中,执行完push方法后,接着执行full_dispatch_request方法,从这个名字中我们也能猜到,这个方法只要是负责请求的分发。

def full_dispatch_request(self):
      
            self.try_trigger_before_first_request_functions()
            try:
                request_started.send(self)
                rv = self.preprocess_request()
                if rv is None:
                    rv = self.dispatch_request()
            except Exception as e:
                rv = self.handle_user_exception(e)
            return self.finalize_request(rv)      
full_dispath_request方法

  在full_dispatch_request方法中先执行preprocess_request方法,这个方法,会先执行所有被before_request装饰器装饰的函数,然后就通过路由的分发执行视图函数了(dispatch_request)

执行视图函数时

  在执行视图函数之前,先执行了before_request,在执行我们的视图函数。

  视图函数主要处理业务逻辑。在视图函数中可以调用request对象,进行取值,也可以调用session对象对session的存取。

  在整个request的请求生命周期中,获取请求的数据直接调用request即可,对session进行操作直接调用session即可。request和session都是LocalProxy对象,借助偏函数的概念将对应的值传入_lookup_req_object函数。先从_request_ctx_stack(LocalStack)对象中获取ctx(请求上下文对象),再通过反射分别获取request和session属性。整个过程中LocalStack扮演了一个全局仓库的角色,请求进来将数据存取,需要时即去即用。所以,flask实现了在整个请求的生命周期中哪儿需要就直接调用的特色。

request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))

请求结束前

  视图函数执行完后,dispatch_request执行结束,执行full_dispatch_request方法的返回值finalize_request方法。这个方法中,同样的,在返回响应之前,先执行所有被after_request装饰器装饰的函数。

---->finalize_request ------>
def process_response(self, response):
        
        ctx = _request_ctx_stack.top
        bp = ctx.request.blueprint
        funcs = ctx._after_request_functions
        if bp is not None and bp in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
        if None in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[None]))
        for handler in funcs:
            response = handler(response)
        if not self.session_interface.is_null_session(ctx.session):
            self.session_interface.save_session(self, ctx.session, response)
        return response
process_response

  执行process_response过程中,执行完after_request后,然后,执行session的save_session方法。将内存中保存在ctx.session的值取到后,json.dumps()序列化后,写入响应的cookie中(set_cookie),最后返回响应。

def save_session(self, app, session, response):
        domain = self.get_cookie_domain(app)
        path = self.get_cookie_path(app)

        # If the session is modified to be empty, remove the cookie.
        # If the session is empty, return without setting the cookie.
        if not session:
            if session.modified:
                response.delete_cookie(
                    app.session_cookie_name,
                    domain=domain,
                    path=path
                )

            return

        # Add a "Vary: Cookie" header if the session was accessed at all.
        if session.accessed:
            response.vary.add('Cookie')

        if not self.should_set_cookie(app, session):
            return

        httponly = self.get_cookie_httponly(app)
        secure = self.get_cookie_secure(app)
        samesite = self.get_cookie_samesite(app)
        expires = self.get_expiration_time(app, session)
        val = self.get_signing_serializer(app).dumps(dict(session))
        # set_cookie将session写入响应的cookie中
        response.set_cookie(
            app.session_cookie_name,
            val,
            expires=expires,
            httponly=httponly,
            domain=domain,
            path=path,
            secure=secure,
            samesite=samesite
        )
save_session

  返回响应后,自动的调用ctx.auto_pop(error),将Local中存储的ctx对象pop掉,整个请求结束。

请求上下文的执行流程:

 

 

 

应用上下文

  与请求上下文类似,当请求进来时,先实例化一个AppContext对象app_ctx,在实例化的过程中,提供了两个有用的属性,一个是app,一个是g。self.app就是传入的全局的app对象,self.g是一个全局的存储值的对象。接着将这个app_ctx存放到LocalStack()。

class AppContext(object):
  def __init__(self, app):
    self.app = app
    self.url_adapter = app.create_url_adapter(None)
    self.g = app.app_ctx_globals_class()  

  视图函数中,我们就可以调用app对象和g对象,如果我们使用蓝图构建我们的项目时,在每一个直接引用app就会造成循环引用的异常,这时,应用上下文就会显得非常有用,我们可以直接调用current_app就可以在整个生命周期中使用我们的app对象了。比如使用我们的配置项:current_app.config

current_app = LocalProxy(_find_app)

  最后,当视图函数执行结束后,从storage中pop掉app_ctx对象。

from flask import Flask,request,session,g

app = Flask(__name__)  # type:Flask

@app.before_request
def auth_demo():
    g.val = 123

@app.route('/')
def index():
    print(g.val)
    return "Hello World"

if __name__ == '__main__':
    app.run()
g的简单使用示例

  总结:flask中的上下文管理机制分为请求上下文和应用上下文两大方面,通过上下文的管理机制,实现了即去即用的一个特色。

转载于:https://www.cnblogs.com/zxmbky/p/10022701.html

<think>好的,我现在需要帮用户解答Flask应用上下文请求上下文的区别。首先,我得回顾一下相关的知识,确保自己理解正确。根据用户提供的引用资料,Flask有两种上下文应用上下文请求上下文。这两者的区别可能涉及它们的定义、生命周期、存储的数据以及使用场景。 首先,应用上下文。根据引用[2]引用[5],应用上下文(application context)与Flask应用实例相关。它存储的是应用级别的配置信息,比如数据库连接、应用配置等。应用上下文应用启动时被创建,并在整个应用运行期间存在,但可能因为请求的处理而被暂时推入或弹出。引用[3]提到应用上下文用于缓存资源,比如数据库连接,而且因为应用上下文应用扩展共享,所以需要唯一的变量名。 然后是请求上下文。引用[1][4]指出,请求上下文(request context)处理的是HTTP请求相关的数据,如request对象session。每次客户端发起请求时,服务器会创建请求上下文请求处理完毕后就会被销毁。请求上下文的生命周期较短,仅限于单次请求期间。例如,request对象封装了客户端请求的信息,而session用于保持用户会话状态。 接下来需要比较两者的区别。生命周期是一个关键点:应用上下文的生命周期比请求上下文长,一个应用存活期间可能有多个请求,每个请求都有自己的上下文。存储的数据类型也不同,应用上下文保存应用级别的配置资源,而请求上下文保存请求相关的数据。此外,它们的创建销毁时机不同,应用上下文可能在应用启动时或需要时创建,而请求上下文在每个请求到来时创建,请求结束后销毁。 还要注意全局变量如current_appg属于应用上下文,而requestsession属于请求上下文。这点在引用[4][5]中都有提到。g变量虽然存在于应用上下文中,但它是每次请求都会重置的临时存储,这可能容易让人混淆,需要明确说明。 用户可能想知道为什么需要这两种上下文,以及它们如何协同工作。例如,应用上下文允许在请求之外访问应用配置,比如在命令行或后台任务中使用current_app。而请求上下文确保每个请求的数据隔离,避免信息泄露。 可能还需要解释Flask上下文栈机制,应用上下文请求上下文都是通过栈来管理的,这样可以在需要时推入弹出,尤其是在处理多个应用请求时,如测试或使用多个应用实例。 最后,总结它们的区别,确保结构清晰,分点说明。同时根据用户提供的引用资料,正确标注引用来源,比如应用上下文的共享资源请求上下文的每次请求独立。 在生成相关问题的时候,可以考虑用户可能进一步想知道如何使用这些上下文,或者具体例子,比如如何在视图函数外访问上下文,或者如何处理多线程中的上下文问题。</think>### Flask应用上下文请求上下文的区别 #### 1. **定义与核心作用** - **应用上下文** 存储与**Flask应用实例**相关的全局配置资源,例如数据库连接、应用密钥等。通过变量`current_app`(当前应用实例)`g`(请求期间的临时存储)体现[^3][^4][^5]。 **生命周期**:与应用实例共存,但在实际运行中可能被多次推入/弹出上下文栈(例如后台任务或命令行操作时)[^2][^5]。 - **请求上下文** 封装**单次HTTP请求**的交互数据,例如客户端请求内容(`request`对象)用户会话(`session`字典)。 **生命周期**:仅存在于一次HTTP请求从接收到响应的过程中,请求结束后自动销毁[^1][^4]。 #### 2. **生命周期对比** | 上下文类型 | 创建时机 | 销毁时机 | |----------------|----------------------------|--------------------------| | 应用上下文 | 应用启动或首次请求处理时 | 应用关闭或手动移除时 | | 请求上下文 | 每次HTTP请求到达时 | HTTP请求处理完成后 | #### 3. **典型应用场景** - **应用上下文** - 在非请求场景(如CLI命令)中访问应用配置[^2]。 - 跨请求共享资源(如数据库连接池)[^3]。 ```python from flask import current_app # 在视图函数外获取应用配置 db_url = current_app.config['DATABASE_URI'] ``` - **请求上下文** - 获取客户端请求参数(如`request.args`)[^1]。 - 维护用户会话状态(如`session['user_id']`)[^4]。 ```python from flask import request @app.route('/login', methods=['POST']) def login(): username = request.form['username'] # 从请求上下文中获取数据 session['user'] = username # 操作请求上下文中的session ``` #### 4. **全局变量与数据隔离** - **应用上下文变量** - `current_app`:始终指向当前活动的应用实例[^2]。 - `g`:单次请求内的临时存储,每次请求重置(尽管属于应用上下文,但其数据与请求绑定)[^4]。 - **请求上下文变量** - `request`:包含HTTP请求的URL参数、表单数据等[^1]。 - `session`:跨请求的用户会话存储(依赖客户端Cookie加密)[^4]。 #### 5. **设计原理与协作** Flask通过**上下文栈**管理这两种上下文: - 请求到来时,**请求上下文****应用上下文**会被依次推入栈中[^2]。 - 视图函数通过栈顶自动获取当前上下文(如`request.url`实际调用栈顶的请求上下文)。 - 多应用或多请求场景(如测试时)可通过手动管理上下文栈实现隔离[^3]。 --- 相关问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值