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

Flask

flask和django的区别

  1. Django功能大而全,Flask只包含基本的配置;

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

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

  3. Flask 比 Django 更灵活 ,

  4. flask非常适用于开发API接口

一、flask配置文件

  1. 导入类的配置文件(导入类的路径)

    • 方法

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

      2. 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. 利用importlib模块中的import_module函数:

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

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

      3. 获取到类之后遍历类的属性,在利用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,
    }
    
  • 方式二

    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']
    '''
    
  • 规则

    属性 说明 示例
    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)

自定义正则路由

三、蓝图

  • 好处:
    1. 目录结构的划分
    2. 可以单独给一个蓝图加前缀
    3. 应用特殊装饰器-before_request

创建蓝图

  1. 创建一个和项目名相同的文件夹 (” crm“ ------文件夹)

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

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

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

  2. 在视图文件夹 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'
    
  3. 在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 
    
  4. 创建静态文件夹static和模板文件夹templates

    crm						// 工程文件夹
    |-- crm					// 蓝图文件夹
    |  |-- static			// 蓝图-静态文件夹
    |  |-- templates		// 蓝图-模板文件夹
    |  |-- views			// 蓝图-视图文件夹
    |      |-- account.py   // 蓝图-视图文件
    |  |-- __init__.py		// 初始化
    |-- manage.py			// 主程序
    

自定义蓝图的static文件夹和trmplates文件夹

  1. 蓝图-视图文件中创建蓝图对象时,给出静态文件夹名字

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

  2. 注意:

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

为某一个蓝图内所有URL路由访问地址加前缀

  1. __init__.py文件中

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

before_request–访问URL先触发

  1. 直接在app下装饰函数(在主程序文件中)

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

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

    例:有一个ac蓝图

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

    @ac.before_request
    def f1():
        print('ac.before_request')
    

四、子域名

  • 蓝图子域名: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'
# app.py
app = Flask(__name__)
app.config['SERVER_NAME'] = 'example.com'
from modules import public
app.register_blueprint(public, subdomain='public')

现在可以通过public.example.com/来访问public模块了。

通配符子域

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

# modules.py
from flask import Blueprint
public = Blueprint('public', __name__)
@member.route('/')
def home():
    return g.subdomain
# app.py
app = Flask(__name__)
app.config['SERVER_NAME'] = 'example.com'
from modules import public
app.register_blueprint(public, subdomain='<subdomain>')

这里的subdomain使用了动态参数<subdomain>(路由中的URL变量也是这种方式)。我们可以用这个参数在请求回调函数之前利用的组合的url处理器来获取相关的用户。这样我们就可以通过*.example.com的形式来访问member模块。

*五、 threading.local–(和flask没有关系)

  1. 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()
    
  2. 获取线程的唯一标记

    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()
    
  3. 自定义类似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}
            
            
    

* 六、请求上下文管理(源码剖析)

session

flask中 session的流程详解

  1. 客户端的请求进来时,会调用app.wsgi_app():

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

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

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

  5. 源码:

    # 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)
    
    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()
    
  6. 当请求第二次到来时,与第一次的不同就在open_session()那个val判断处,此时cookies不为空, 获取cookie的有效时长,如果cookie依然有效,通过与写入时同样的签名算法将cookie中的值解密出来并写入字典并返回中,若cookie已经失效,则仍然返回’空字典’。

request

flask中 request的流程详解(和session一样)

  1. 客户端的请求进来时,会调用app.__call__app.wsgi_app():

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

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

  4. 生成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
      
flask请求流程图

每个请求都流一遍,多线程协程中每个‘程’中都创建新的对象和属性包括(app, ctx, LocalStack,Local)

flask中request和esession的请求流程

上下文原理

线程(协程)的值相互隔离(独立空间)。

#!/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()

自定义线程(协程)的Local类

模拟栈的操作

  1. 创建一个类

  2. 创建一个字典

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

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

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)  # ['老王','老李']
'''
操作起来太麻烦,看下面
'''       
方便操作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())

拥有了这个类,使操作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'
    
  • 定义一个自动取到上面的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)
    
  • 偏函数
    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())
    

参考武沛齐的博客

flask-session

  1. pip3 install falsk-session
  2. from flask_session import Session老版本的:from flask.ext.session import Session
  3. 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'>)
'''

面向对象中特殊的方法

def __getattr__(self,item)#--------------->  使用 (.)点 的时候触发,item为点后面的值
def __setattr__(self,key,value)#----------> obj.xx=123 时,key=xx,value=123

八、Flask数据库

暂无!等待更新

数据库连接池

pymysql

猜你喜欢

转载自blog.csdn.net/gh7735555/article/details/84972226
今日推荐