• Flask框架详细上下文管理机制


    原创gao.xiangyang 最后发布于2018-12-12 14:20:14 阅读数 1199 收藏
    展开
    文章目录
    Flask
    flask和django的区别
    一、flask配置文件
    二、路由系统
    自定义正则路由
    三、蓝图
    创建蓝图
    自定义蓝图的static文件夹和trmplates文件夹
    为某一个蓝图内所有URL路由访问地址加前缀
    before_request--访问URL先触发
    四、子域名
    一般固定子域名
    通配符子域
    *五、 threading.local--(和flask没有关系)
    * 六、请求上下文管理(源码剖析)
    session
    flask中 session的流程详解
    request
    flask中 request的流程详解(和session一样)
    flask请求流程图
    上下文原理
    线程(协程)的值相互隔离(独立空间)。
    自定义线程(协程)的Local类
    Flask上下文管理源码剖析
    Local类
    方便操作Local的类
    session和requste在Flask中的原理
    定义一个自动取到上面的`obj`的函数,变量名和函数名参照`Flask`源码
    偏函数
    flask-session
    七、其他
    执行父类的方法
    面向对象中特殊的方法
    八、Flask数据库
    数据库连接池
    pymysql
    Flask
    flask和django的区别
    Django功能大而全,Flask只包含基本的配置;

    Django有模板,表单,路由,认证,基本的数据库管理等等内建功能。

    与之相反,Flask只是一个内核,默认依赖于两个外部库: Jinja2 模板引擎和 Werkzeug WSGI 工具集,其他很多功能都是以扩展的形式进行嵌入使用。

    Flask 比 Django 更灵活 ,

    flask非常适用于开发API接口

    一、flask配置文件
    导入类的配置文件(导入类的路径)

    方法

    首先创建一个PY文件,写一个类,类中写一些配置信息的静态字段

    app.config.from_object("python类或类的路径")

    def from_object(self, obj):
    if isinstance(obj, string_types): # 判断是否是字符串
    obj = import_string(obj) # 利用import_module封装的函数拿到类
    for key in dir(obj): # 遍历自己写的配置类
    if key.isupper(): # 判断类的属性是不是大写
    self[key] = getattr(obj, key) # 保存进配置信息

    1
    2
    3
    4
    5
    6
    7
    原理

    利用importlib模块中的import_module函数:

    o = importlib.import_module("aa.bb")

    利用getattr('var1')获取类中或者模块中的属性

    获取到类之后遍历类的属性,在利用getattr()获取所有大写的类的静态变量,写进config配置文件字典

    二、路由系统
    方式一

    @app.route('/user/<username>')
    @app.route('/post/<int:post_id>')
    @app.route('/post/<float:post_id>')
    @app.route('/post/<path:path>')
    @app.route('/login', methods=['GET', 'POST'])

    # 路由中的数据类型
    DEFAULT_CONVERTERS = {
    'default': UnicodeConverter,
    'string': UnicodeConverter,
    'any': AnyConverter,
    'path': PathConverter,
    'int': IntegerConverter,
    'float': FloatConverter,
    'uuid': UUIDConverter,
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    方式二

    def index():
    pass
    app.add_url_rule('/', 'index', index)

    '''
    add_url_rule(self,rule,endpoint=None,view_func=None,
    provide_automatic_options=None,**options)

    rule-----------------------------URL规则为字符串
    endpoint-------------------------字符串,端点名字,不设置默认为函数名
    view_func------------------------函数名
    provide_automatic_options-------- 未设置provide_automatic_options,则进入默认的OPTIONS请求回应,否则请求endpoint匹配的函数执行,并返回内容
    options -------------------------请求方式,options=['GET','POST']
    '''
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    规则

    属性 说明 示例
    Truestrict_slashes False不严格,True严格,默认=None @app.route('/index',strict_slashes=False)
    redirect_to 重定向到指定地址 @app.route('/index/<int:nid>',redirect_to='/home/<nid>'
    @app.route('/index/<int:nid>', redirect_to=func)
    subdomain 子域名访问(必须配置’SERVER_NAME’) app.config['SERVER_NAME'] = 'wupeiqi.com:5000'
    @app.route("/", subdomain="admin")
    子域名带正则 @app.route("/dynamic", subdomain="<username>")
    def username_index(username)
    自定义正则路由
    三、蓝图
    好处:
    目录结构的划分
    可以单独给一个蓝图加前缀
    应用特殊装饰器-before_request
    创建蓝图
    创建一个和项目名相同的文件夹 (” crm“ ------文件夹)

    在新文件夹(” crm“)内创建__init__.py文件,并在里面实例化

    from flask import Flask
    def create_app():
    app = Flask(__name__) # 实例化flask
    return app
    1
    2
    3
    4
    创建主程序文件xxx.py

    from crm import create_app # 导入自己写的实例化flask文件
    app = create_app()
    if __name__ == '__main__':
    app.run()
    1
    2
    3
    4
    在新文件夹(” crm“)中创建视图文件夹 views

    在视图文件夹 views下可创建多个视图python文件

    例:account.py

    from flask import Blueprint # 导入蓝图

    ac = Blueprint('ac',__name__) # 创建蓝图对象

    @ac.route('/login')
    def login():
    return 'login'

    @ac.route('/login')
    def login():
    return 'login'
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    在crm文件夹下的__init__.py文件创建视图文件与主程序的关系

    from flask import Flask
    from .views.account import ac # 导入视图文件

    def create_app():
    app = Flask(__name__)
    app.register_blurprint(ac) # 将蓝图注册到app
    return app
    1
    2
    3
    4
    5
    6
    7
    创建静态文件夹static和模板文件夹templates

    crm // 工程文件夹
    |-- crm // 蓝图文件夹
    | |-- static // 蓝图-静态文件夹
    | |-- templates // 蓝图-模板文件夹
    | |-- views // 蓝图-视图文件夹
    | |-- account.py // 蓝图-视图文件
    | |-- __init__.py // 初始化
    |-- manage.py // 主程序
    1
    2
    3
    4
    5
    6
    7
    8
    自定义蓝图的static文件夹和trmplates文件夹
    在 蓝图-视图文件中创建蓝图对象时,给出静态文件夹名字

    ac = Blueprint('ac', __name__, template_folder='xxxx',static_url_path='xxx')

    注意:

    app在寻找模板文件和静态文件时,先从最外层的templates文件找,找不到才到自定义的template_folder里面找

    为某一个蓝图内所有URL路由访问地址加前缀
    在__init__.py文件中

    app.register_blurprint(ac, url_prefix='/xxxx')

    before_request–访问URL先触发
    直接在app下装饰函数(在主程序文件中)

    这样任意一个URL访问进来都会触发这个被装饰的函数

    @app.before_request
    def f1():
    print('app.before_request')
    1
    2
    3
    在蓝图对象下装饰函数(在蓝图文件中)

    例:有一个ac蓝图

    这样只有当前蓝图下的URL访问进来时才会触发这个函数

    @ac.before_request
    def f1():
    print('ac.before_request')
    1
    2
    3
    四、子域名
    蓝图子域名:xxx = Blueprint(‘account’, name, subdomain=‘admin’)

    前提需要给配置SERVER_NAME: app.config[‘SERVER_NAME’] = ‘abc.com:5000’

    访问时:admin.abc.com:5000/login.html

    一般固定子域名
    一般用于数量比较少的子域名,一个模块对应一个子域名。先看下面一个例子:

    # modules.py
    from flask import Blueprint # 蓝图
    public = Blueprint('public', __name__)
    @public.route('/')
    def home():
    return 'hello flask'
    1
    2
    3
    4
    5
    6
    # app.py
    app = Flask(__name__)
    app.config['SERVER_NAME'] = 'example.com'
    from modules import public
    app.register_blueprint(public, subdomain='public')
    1
    2
    3
    4
    5
    现在可以通过public.example.com/来访问public模块了。

    通配符子域
    通配符子域,即通过一个模块来匹配很多个子域名。比如某些网站提供的个性化域名功能,就是这种形式。

    # modules.py
    from flask import Blueprint
    public = Blueprint('public', __name__)
    @member.route('/')
    def home():
    return g.subdomain
    1
    2
    3
    4
    5
    6
    # app.py
    app = Flask(__name__)
    app.config['SERVER_NAME'] = 'example.com'
    from modules import public
    app.register_blueprint(public, subdomain='<subdomain>')
    1
    2
    3
    4
    5
    这里的subdomain使用了动态参数<subdomain>(路由中的URL变量也是这种方式)。我们可以用这个参数在请求回调函数之前利用的组合的url处理器来获取相关的用户。这样我们就可以通过*.example.com的形式来访问member模块。

    *五、 threading.local–(和flask没有关系)
    local为每个线程的数据开辟一个独立的空间,使得线程对自己空间中的数据进行操作(数据隔离)

    import threading
    from threading import local
    import time

    obj = local()

    def task(i):
    obj.xxx = i
    time.sleep(1)
    print(obj.xxx,i)

    for i in range(10):
    t = threading.Thread(target = task,args=(i,))
    t.start()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    获取线程的唯一标记

    import threading
    import time
    def task(i):
    print(threading.get_ident(),i)

    for i in range(10):
    t = threading.Thread(target = task,args=(i,))
    t.start()
    1
    2
    3
    4
    5
    6
    7
    8
    自定义类似local的函数—Local 为每个协程或者线程开辟独立空间

    import threading
    try:
    import greenlet # 导入协程模块
    get_ident = greenlet.getcurrent # 获得协程唯一标识函数
    except:
    get_ident = threading.get_ident # 获得线程唯一标识函数

    class Local(object):
    DIC = {}
    def __getattr__(self,item):
    ident = get_ident()
    if ident in self.DIC:
    return self.DIC[ident].get(item)
    else:
    return None

    def __setattr__(self,key,balue):
    ident = get_ident()
    if ident in self.DIC:
    self.DIC[ident][key] = value
    else:
    self.DIC[ident] = {key:value}


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    * 六、请求上下文管理(源码剖析)
    session
    flask中 session的流程详解
    客户端的请求进来时,会调用app.wsgi_app():

    此时,会生成一个ctx,其本质是一个RequestContext对象:

    在RequestContext 对象中定义了session,且初值为None。

    接着继续看wsgi_app函数中,ctx.push()函数,会返回一个session对象保存在ctx中(详解下面代码)

    源码:

    # ctx.push()中session的相关代码

    if self.session is None:
    #session_interface = SecureCookieSessionInterface()
    session_interface = self.app.session_interface
    self.session = session_interface.open_session(self.app, self.request) # open函数在下面
    if self.session is None:
    self.session = session_interface.make_null_session(self.app)
    1
    2
    3
    4
    5
    6
    7
    8
    def open_session(self, app, request):
    # 获取session签名的算法
    s = self.get_signing_serializer(app)
    # 如果为空 直接返回None
    if s is None:
    return None
    # session_cookie_name 是配置信息中的SESSION名字,获取request中的cookies
    val = request.cookies.get(app.session_cookie_name)
    # 如果val为空,即request.cookies为空
    if not val:
    # session_class = SecureCookieSession
    # 看其继承关系,其实就是一个特殊的字典。到此我们知道了session就是一个特殊的字典
    # 并保存在ctx中
    return self.session_class()
    max_age = total_seconds(app.permanent_session_lifetime)
    try:
    data = s.loads(val, max_age=max_age)
    return self.session_class(data)
    except BadSignature:
    return self.session_class()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    当请求第二次到来时,与第一次的不同就在open_session()那个val判断处,此时cookies不为空, 获取cookie的有效时长,如果cookie依然有效,通过与写入时同样的签名算法将cookie中的值解密出来并写入字典并返回中,若cookie已经失效,则仍然返回’空字典’。

    参考Flask-cookies
    flask 源码解析:session
    request
    flask中 request的流程详解(和session一样)
    客户端的请求进来时,会调用app.__call__中app.wsgi_app():

    此时,会生成一个ctx,其本质是一个RequestContext对象(ctx中封装了session和request对象)

    在RequestContext 对象中定义了request = app.request_class(environ)environ包含所有的请求数据

    生成ctx后,ctx对象调用了push函数,push函数中有_request_ctx_stack.push(self),将ctx对象存进_request_ctx_stack中

    _request_ctx_stack---->全局变量,是一个LocalStack()类,下面有讲到这个类的原理,这里略过就可以

    _request_ctx_stack对象中有__storge__={id1:{stack:[ctx对象,]} }(看完下面就明白了)

    这时候可以直接试验一下

    from falsk.globals import _request_ctx_stack
    # 在试图函数中可以直接调用_request_ctx_stack.top
    1
    2
    flask请求流程图
    每个请求都流一遍,多线程协程中每个‘程’中都创建新的对象和属性包括(app, ctx, LocalStack,Local)

    上下文原理
    线程(协程)的值相互隔离(独立空间)。
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import threading
    local_values = threading.local() # 为每个线程开辟空间,类似一个大字典,获取每个线程的唯一标识,每个标识作为一个ID ,然后{ID1:{}, ID2:{}, ID3:{}}
    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()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    自定义线程(协程)的Local类
    模拟栈的操作

    创建一个类

    创建一个字典

    获取到的每个协程(线程)的唯一标记为字典内的键,创建一个新的自定当作独立空间

    {id1:{}, id2:{}, id3:{}}

    # 优先按照协程,次要线程
    try:
    from greenlet import greenlet as get_ident # 获取协程的唯一标记
    except:
    from threading import get_ident # 获取线程的唯一标记
    class Local(object):
    def __init__(self):
    # 创建一个 storage = {},使用父类创建,如果不用父类,那么会调用自己定义的__setattr__函数,会报错
    object.__setattr__(self,'storage',{})
    def __setattr__(self, key, value):
    ident = get_ident()
    try:
    self.storage[ident][key] = value
    except KeyError:
    self.storage[ident] = {key:value}

    def __getattr__(self, item):
    ident = get_ident()
    try:
    return self.storage[ident][item]
    except KeyError:
    print('None')
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    Flask上下文管理源码剖析
    Local类
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-

    # 优先按照协程,次要线程
    try:
    from greenlet import greenlet as get_ident # 获取协程的唯一标记
    except:
    from threading import get_ident # 获取线程的唯一标记
    from threading import local

    # 清除协程(线程)中所有的变量
    def release_local(local):
    local.__release_local__()

    # 定义一个存储协程(线程)空间的类
    class Local(object):
    __slots__ = ('__storage__', '__ident_func__') # 设置类只有这两个变量
    def __init__(self):
    object.__setattr__(self, '__storage__', {}) # self.__storage__ = {}
    object.__setattr__(self, '__ident_func__', get_ident) # self.__ident_func__ = get_ident

    # 清除协程(线程)中的变量
    def __release_local__(self):
    # 提取当前对象的 {id1:{}, id2{}} 的 idx 。如果没有则返回None
    self.__storage__.pop(self.__ident_func__(), None)

    # "." 触发
    def __getattr__(self, name):
    try:
    # 返回当前协程(线程)唯一标识的字典的[name]
    return self.__storage__[self.__ident_func__()][name]
    except KeyError:
    raise AttributeError(name)
    # obj.name=value触发
    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)

    if __name__ == '__main__':
    obj = Local()
    obj.stack = []

    obj.stack.append('老王')
    obj.stack.append('老李')
    print(obj.stack) # ['老王','老李']
    '''
    操作起来太麻烦,看下面
    '''
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    方便操作Local的类

    # 定义方便操作Local的类
    class LocalStack(object):
    def __init__(self):
    # 实例Local()
    self._local = Local()

    # 清除协程(线程)中的变量
    def __release_local__(self):
    self._local.__release_local__()

    # 模拟栈操作,推入
    def push(self, obj):
    # 拿到Local对象中的stack属性,如果没有则返回None
    rv = getattr(self._local, 'stack', None)
    # 如果没找到,说明第一次进入,则创建一个列表用来模拟栈的进出
    if rv is None:
    self._local.stack = rv = []
    # 将obj存进列表最后
    rv.append(obj)
    return rv
    # 模拟栈操作,取出
    def pop(self):
    # 堆栈中删除最上面的项,也就是删除列表最后一项,并取出。如果堆栈已经为空,则为旧值或“None”。
    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

    if __name__ == '__main__':
    obj = LocalStack()
    obj.push('老王')
    obj.push('老李')
    print(obj.top)
    print(obj.pop())
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    拥有了这个类,使操作Local类更快捷,更方便。

    但是没有这个类,也可以操作Local类,只是比较复杂

    session和requste在Flask中的原理
    在上面flask中session中讲到RequestContext的原理,就是保存有session和request的一个类,

    这里示例创建一个RequestContext类

    class RequestContext(object):
    def __init__(self):
    self.session = 'xxxxxxxxxxx'
    self.request = 'ooooooooooo'

    ctx = RequestContext()
    xxx = LocalStack()
    xxx.push(ctx) # { 协程ID1:{stack:[ctx对象, ]} }
    obj = xxx.top # obj = ctx
    print(obj.request) # 'ooooooooooo'
    print(obj.session) # 'xxxxxxxxxxx'
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    定义一个自动取到上面的obj的函数,变量名和函数名参照Flask源码
    def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
    raise RuntimeError(_request_ctx_err_msg)
    # 返回top对象中的name属性
    return getattr(top, name)
    1
    2
    3
    4
    5
    6
    偏函数
    from functools import partial # 详情请百度
    #_lookup_req_object('request')
    request = partial(_lookup_req_object,'request')
    #_lookup_req_object('session')
    session = partial(_lookup_req_object,'session')

    print(request()) # 返回request信息
    print(session())
    1
    2
    3
    4
    5
    6
    7
    8
    参考武沛齐的博客

    flask-session
    pip3 install falsk-session
    from flask_session import Session老版本的:from flask.ext.session import Session
    Session(app)
    七、其他
    执行父类的方法
    class Foo(object):
    def func(self):
    print('Foo.func')
    class Bar(object):
    def func(self):
    print('Bar.func')
    class F1(Foo,Bar):
    pass

    f = F1()
    f.func() # print('Foo.func')
    print(F1.__mro__)
    '''
    (<class '__main__.F1'>,
    <class '__main__.Foo'>,
    <class '__main__.Bar'>,
    <class 'object'>)
    '''
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    面向对象中特殊的方法
    def __getattr__(self,item)#---------------> 使用 (.)点 的时候触发,item为点后面的值
    def __setattr__(self,key,value)#----------> obj.xx=123 时,key=xx,value=123
    1
    2
    八、Flask数据库
    暂无!等待更新

    数据库连接池
    pymysql
    ————————————————
    版权声明:本文为CSDN博主「gao.xiangyang」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/gh7735555/article/details/84972226

  • 相关阅读:
    System.arraycopy用法
    Springmvc Get请求Tomcat、WebLogic中文乱码问题
    Rails内存的问题 Java内存情况
    Java 执行系统命令
    搭建Cocos2d-JS开发环境
    xcode 6 改动组织及开发人员
    poj
    hdu 4869 Turn the pokers (思维)
    【剑指offer】扑克牌的顺子
    NYOJ 480 Fibonacci Again!
  • 原文地址:https://www.cnblogs.com/fengff/p/12510084.html
Copyright © 2020-2023  润新知