面试官:Flask框架中的上下文机制是如何实现的?有什么作用?

本文基于python的Flask框架,分享其上下文机制的实现和原理。在Flask中,上下文机制是最核心的概念之一。不仅仅是因为通过上下文机制实现了客户端和服务器交互简单化的目的,更为了flask的生态发展,可插拔式的组件开发提供了很好的支持。

什么是Flask中的上下文?

生活中的上下文模型

假设说你要去取快递(发送请求),你来到了快递站点,然后你需要告诉工作人员你的快递单号,你的电话号码等个人信息,工作人员才会在系统中(系统中的信息可以理解为已经创建好的上下文代理,只有当你的信息需要被需要时,才会动态生成你的信息)查找,会根据你的提货码将快递取出给你(根据你提供的上下文信息给出响应),并在系统中修改为此快递已被取走(删除此次的上下文信息)

专业化的解释

上下文其实就是你向服务器发送请求时所需要的’资料’(包括请求参数,配置,验证等等),需要的时候取资料,用完则释放

flask中的上下文变量有哪些

flask中的上下文变量一共有4个,分为两类,分别是请求上下文和应用上下文

请求上下文:request和session

  • request:客户端的请求参数
  • session:作为用户唯一标识,一般用来做用户登录状态的记录

app上下文:current_app和g

  • current_app:指向当前应用实例
  • g:一次请求中的全局变量,负责加载当前请求的一些操作,比如说连接数据库,响应结束,连接关闭

源码解读

flask中的上下文变量

# globals.py中的上下文变量
request = LocalProxy(partial(_lookup_req_object, "request"))
session = LocalProxy(partial(_lookup_req_object, "session"))

g = LocalProxy(partial(_lookup_app_object, "g"))
current_app = LocalProxy(_find_app)

我们知道Proxy是代理的意思,那么也就是说,这几个上下文变量其实都是通过代理类实现的,我们研究下这个代理类

代理类

class LocalProxy(object):

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

实例方法中,object.setattr(self, “_LocalProxy__local”, local)这个写法可能有人不太理解,其实等价于self.__local = local

其实就是类的私有变量(双下划线属性)进行外部调用的写法

这个local是必须要传进来的变量,是一个偏函数,那么啥叫偏函数?

偏函数

# 我们要调用此函数
def spam(a, b, c, d):
    print(a, b, c, d)

from functools import partial
# 偏函数,减少传递的参数值
s1 = partial(spam, 1) # s1代表着里面的函数,自动将1传递给a,通过s1调用spam函数直接从b开始传参
# 调用偏函数
s1(2, 3, 4)
# 结果
>>> 1234

通过上述我们知道偏函数其实就是调用原有函数,只不过可以添加需要传递的参数,这样在后面调用偏函数时就可以少传递几个变量,那么我们看下上下文变量的偏函数都干了什么(用request举例)

# 原有函数
def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)

# 偏函数
request = LocalProxy(partial(_lookup_req_object, "request"))

# 我们将原有函数整理下,如下所示
def _lookup_req_object(name):
    # 此时参数name = request
    # 当前请求的上下文类
    top = RequestContext("当前请求的request", "当前请求的session""其他对本内容不重要的参数")
    # 说白了就是将请求上下文中的request(Request类)属性取出来
    obj = top.name
    # 封装好的request属性
    return obj

总结来说:上下文变量request就是通过代理类拿到了请求上下文的RequestContext中的request属性,这个request属性拥有客户端请求的所有参数。

既然属性已经拿到了,就可以使用request.method,request.args等方式进行调用客户端参数,这个调用的原理又是什么呢?

既然是request对象进行调用,而request又是LocalProxy的代理类的对象,我们知道,"对象点方法"的使用,是去类中寻找__getattr__方法,那么我们看下这个方法

LocalProxy.__getattr__方法

这个方法基于反射原理,实现了动态取request对象中存在的方法,看下源码

class LocalProxy(object):
    # 通过反射这个函数
    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.
        """
        # 上面我们说到__local就是偏函数,所以这里返回的就是request
        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__)
    
    # 如果是调用request.method,那么此时参数name=method
    def __getattr__(self, name):
        if name == "__members__":
            return dir(self._get_current_object())
        return getattr(self._get_current_object(), name)

通过上述的过程,request对象成功的找到了method方法。同理,但凡是需要使用的客户端的请求参数,就是在这里根据不同的name进行反射提取

其他几个上下文变量实现的方法大同小异,都是通过代理类(除了current_app),偏函数,反射实现,这里就不作一一的解释了。

应用上下文作用和意义

官方解释

应用上下问存在的主要原因是,在过去,请求上下文被附加了一堆函数,但是又没有什么好的解决方案。因为 Flask 设计的支柱之一是你可以在一个 Python 进程中拥有多个应用。

那么代码如何找到“正确的”应用?在过去,我们推荐显式地到处传递应用,但是这会让我们在使用不是以这种理念设计的库时遇到问题。

解决上述问题的常用方法是使用后面将会提到的current_app代理对象,它被绑定到当前请求的应用的引用。既然无论如何在没有请求时创建一个这样的请求上下文是一个没有必要的昂贵操作,应用上下文就被引入了。

我的理解

这样设计的优势在于上下文的推送和删除是动态进行的,如果需要使用,则动态的加载,不需要使用,则放在内存中不用管。

我是一名奋战在编程界的pythoner,工作中既要和数据打交道,也要和erp系统,web网站保持友好的沟通……时不时的会分享一些提高效率的编程小技巧,在实际应用中遇到的问题以及解决方案,或者源码的阅读等等,欢迎大家一起来讨论!如果觉得写得还不错,欢迎关注点赞,谢谢。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值