• Flask之配置文件,蓝图,数据库连接池,上下文原理


    一、Flask配置文件

    1. 配置文件原理

    配置文件本质上是使用的地址,实现方式只有两种方法

    - setitem

    class Foo():
        def __setitem__(self,key,value):
            print(key,value)
    
    obj = Foo()
    obj['xxx'] = 123
    View Code

    - 继承字典

    class Foo(dict):
        def __init__(self,val):
            dict.__init__(self,val)
    
    obj = Foo({'xxx':123})
    print(obj)
    View Code

    2. 设置方式

    - 方式一,使用字典方式配置

    app.config['SESSION_COOKE_NAME'] = 'session_liling'

    - 方式二,引入文件,设置

    # s1.py
    from flask import Flask
    
    app = Flask(__name__)
    
    app.config.from_pyfile('settings.py')    # 引用settings.py中的AAAA
    print(app.config['AAAA'])        # 123
    
    # settings.py
    AAAA = 123

    - 方法三,使用环境变量设置,推荐使用

    from flask import Flask
    
    app = Flask(__name__)
    
    import os
    
    os.environ['FLASK-SETTINGS'] = 'settings.py'
    
    app.config.from_envvar('FLASK-SETTINGS')

    - 方式四,通过对象方式导入使用,可根据不同环境选择不同的配置,推荐使用

    # s1.py
    from flask import Flask
    
    app = Flask(__name__)
    
    app.config.from_object('settings.BaseConfig')
    print(app.config['NNNN'])   # 123
    
    # settings.py
    
    class BaseConfig(object):  # 公用配置
        NNNN = 123
    
    class TestConfig(object):
        DB = '127.0.0.1'
    
    class DevConfig(object):
        DB = '192.168.1.1'
    
    class ProConfig(object):
        DB = '47.18.1.1'

     3. 不同的文件引用配置

    from flask import Flask,current_app
    
    app = Flask(__name__)
    
    app.secret_key = 'adfadsfhjkhakljsdfh'
    
    app.config.from_object('settings.BaseConfig')
    
    @app.route('/index')
    def index():
        print(current_app.config['NNNN'])
        return 'xxx'
    
    if __name__ == '__main__':
        app.run()

    4. instance_path参数和instance_relative_config参数

    from flask import Flask,current_app
    
    app = Flask(__name__,instance_path=None,instance_relative_config=False)
    # 默认instance_relative_config = False,instance_relative_config和instance_path都不会生效
    # instance_relative_config=True,instance_path才会生效,app.config.from_pyfile('settings.py')将会失效
    # 配置文件找的路径,按instance_path的值作为配置文件路径
    # 默认instance_path=None,None会按照当前路径下的instance文件夹为配置文件的路径
    # 如果设置路径,按照设置的路径查找配置文件。
    
    app.config.from_pyfile('settings.py')
    
    @app.route('/index')
    def index():
        print(current_app.config['NNNN'])
        return 'xxx'
    
    
    if __name__ == '__main__':
        app.run()
    View Code

    二、蓝图

    对应用程序的目录结构进行分配,一般适用于小中型企业

    - 目录结构

    - 内容

    # crm/__init__.py
    # 创建flask项目,用蓝图注册不同的模块
    
    from flask import Flask
    from .views import account
    from .views import order
    
    app = Flask(__name__)
    
    app.register_blueprint(account.account)
    app.register_blueprint(order.order)
    
    ------------------------------------------------------------------------
    # manage.py
    # 启动文件
    import crm
    
    if __name__ == '__main__':
        crm.app.run()
    
    ------------------------------------------------------------------------
    # crm/views/account.py
    # 视图函数模块,Blueprint,将函数引入app
    from flask import Blueprint
    
    account = Blueprint('account',__name__,url_prefix='/xxx')
    
    @account.route('/login')
    def login():
        return 'Login'
    View Code

     三、Flask之数据库连接池

    1. 为什么时候数据库连接池

    - 多连接

      如果不使用数据库连接池,会造成每次请求反复常见数据库连接,数据库会耗费过多资源,数量过大的话,数  据库会过载。

      每次连接数据库会有延迟,也会造成程序运行缓慢。

    - 单链接

      在程序中全局中创建连接,不管关闭连接,程序会一直使用一个连接,避免了反复连接造成的问题。

      但是pymysql模块只支持一个线程的连接,在无法实现多线程。

    2. 基于DBUtils实现数据库连接池

    数据库连接池避免每次操作都要连接数据库

    一直使用一个连接,多线程也会出现问题,可加锁,但变为串行

    import pymysql
    import threading
    from threading import RLock
    LOCK = RLock()
    CONN = pymysql.connect(host='127.0.0.1',
                            port = 3306,
                            user = 'root',
                            password = '123',
                            database = 'ok1',
                            charset = 'utf8'
                            )
    def task(arg):
        with LOCK:
            cursor = CONN.cursor()
            cursor.execute('select * from book')
            result = cursor.fetchall()
            cursor.close()
            print(result)
    for i in range(10):
        t = threading.Thread(target=task,args=(i,))
        t.start()
    View Code

    - 本地线程

      本地线程可实现,线程之间的数据隔离

    import threading
    import time
    # 本地线程对象
    local_values = threading.local()
    
    def func(num):
    
        """
        # 第一个线程进来,本地线程对象会为他创建一个
        # 第二个线程进来,本地线程对象会为他创建一个
        {
            线程1的唯一标识:{name:1},
            线程2的唯一标识:{name:2},
        }
        :param num: 
        :return: 
        """
        local_values.name = num # 4
        # 线程停下来了
        time.sleep(2)
        # 第二个线程: local_values.name,去local_values中根据自己的唯一标识作为key,获取value中name对应的值
        print(local_values.name, threading.current_thread().name)
    
    
    for i in range(5):
        th = threading.Thread(target=func, args=(i,), name='线程%s' % i)
        th.start()
    View Code

    - 模式一,每个线程创建一个连接

      基于threading.local实现创建每个连接。

      每个线程会创建一个连接,该线程没有真正关闭。

      再次调用该线程时,还是使用原有的连接。

      线程真正终止的时候,连接才会关闭。

    from DBUtils.PersistentDB import PersistentDB
    import pymysql
    
    POOL = PersistentDB(
        creator=pymysql,  # 使用链接数据库的模块
        maxusage=None,  # 一个链接最多被重复使用的次数,None表示无限制
        setsession=[],  # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
        ping=0,
        # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always
        closeable=False,
        # 如果为False时, conn.close() 实际上被忽略,供下次使用,再线程关闭时,才会自动关闭链接。如果为True时, conn.close()则关闭链接,那么再次调用pool.connection时就会报错,因为已经真的关闭了连接(pool.steady_connection()可以获取一个新的链接)
        threadlocal=None,  # 本线程独享值得对象,用于保存链接对象,如果链接对象被重置
        host='127.0.0.1',
        port=3306,
        user='root',
        password='123',
        database='pooldb',
        charset='utf8'
    )
    
    
    def func():
        # conn = SteadyDBConnection()
        conn = POOL.connection()
        cursor = conn.cursor()
        cursor.execute('select * from tb1')
        result = cursor.fetchall()
        cursor.close()
        conn.close() # 不是真的关闭,而是假的关闭。 conn = pymysql.connect()   conn.close()
    
        conn = POOL.connection()
        cursor = conn.cursor()
        cursor.execute('select * from tb1')
        result = cursor.fetchall()
        cursor.close()
        conn.close()
    
    import threading
    
    for i in range(10):
        t = threading.Thread(target=func)
        t.start()
    View Code

    - 模式二,线程复用连接池(推荐使用)

      创建一个连接池,为所有线程提供连接,线程使用连接时获取连接,使用完毕放回连接池。

      线程不断地重用连接池里的连接。

    import time
    import pymysql
    import threading
    from DBUtils.PooledDB import PooledDB, SharedDBConnection
    POOL = PooledDB(
        creator=pymysql,  # 使用链接数据库的模块
        maxconnections=6,  # 连接池允许的最大连接数,0和None表示不限制连接数
        mincached=2,  # 初始化时,链接池中至少创建的空闲的链接,0表示不创建
    
    
        maxcached=5,  # 链接池中最多闲置的链接,0和None不限制
        maxshared=3,  # 链接池中最多共享的链接数量,0和None表示全部共享。PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。
        blocking=True,  # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
        maxusage=None,  # 一个链接最多被重复使用的次数,None表示无限制
        setsession=[],  # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
        ping=0,
        # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always
        host='127.0.0.1',
        port=3306,
        user='root',
        password='123456',
        database='flask_test',
        charset='utf8'
    )
    
    
    def func():
        # 检测当前正在运行连接数的是否小于最大链接数,如果不小于则:等待或报raise TooManyConnections异常
        # 否则
        # 则优先去初始化时创建的链接中获取链接 SteadyDBConnection。
        # 然后将SteadyDBConnection对象封装到PooledDedicatedDBConnection中并返回。
        # 如果最开始创建的链接没有链接,则去创建一个SteadyDBConnection对象,再封装到PooledDedicatedDBConnection中并返回。
        # 一旦关闭链接后,连接就返回到连接池让后续线程继续使用。
    
        # PooledDedicatedDBConnection
        conn = POOL.connection()
    
        # print(th, '链接被拿走了', conn1._con)
        # print(th, '池子里目前有', pool._idle_cache, '
    ')
    
        cursor = conn.cursor()
        cursor.execute('select * from userinfo')
        result = cursor.fetchall()
        print(result)
        conn.close()
    
        conn = POOL.connection()
    
        # print(th, '链接被拿走了', conn1._con)
        # print(th, '池子里目前有', pool._idle_cache, '
    ')
    
        cursor = conn.cursor()
        cursor.execute('select * from userinfo')
        result = cursor.fetchall()
        conn.close()
        #
    
    func()
    View Code

     四、Flask上下文管理

    所谓上下文,像考试题目根据上下文,回答一下问题。

    程序中,泛指的外部环境,像wsgi来的网络请求,而且通常只有上文。

    flask中的上下文,被使用在current_app,session,request上。

    以request为例,请求进来首先调用flask的__call__方法,返回wsgi_app,

    并将请求加入wsgi_app,wsgi_app将请求加入实例化对象请求上下文中(ctx),

    之后调用ctx.push方法,其实是在求情上下文栈中(_reqeust_ctx_stack.push)压入了请求,

    _reqeust_ctx_stack其实为LocakStack()类内部构建方法中self._local = Local()了本地线程

    在Local中请求压入了类似字典格式的对象中。通过每个线程的唯一标识的value的key(stack)的value保存请求信息:{ 111:{'stack':[] },222:{'stack':[] }  }。

    每个请求一个线程,push保存请求,通过LocalProxy中的Local的top方法获取请求,当线程结束的时候pop请求。

    1. flask本地线程

    from flask import session
    
    try:
        from greenlet import getcurrent as get_ident        # grenlet协程模块
    except ImportError:
        try:
            from thread import get_ident
        except ImportError:
            from _thread import get_ident                   # get_ident(),获取线程的唯一标识
    
    class Local(object):                                    # 引用session中的LocalStack下的Local
        __slots__ = ('__storage__', '__ident_func__')       # __slots__该类在外面调用时,只能调用定义的字段,其他的不能调用
    
        def __init__(self):
            # object.__setattr__为self设置值,等价于self.__storage__ = {}
            # 为父类object中包含的__steattr__方法中的self.__storage__ = {}
            # 由于类内包含__steattr__,self.xxx(对象.xxx)时会自动会触发__steattr__,
            # 当前__steattr__中storage = self.__storage__又会像self.xxx要值,故会造成递归
            # 所以在父类中__steattr__方法赋值,避免self.xxx调用__setattr__造成的递归
            object.__setattr__(self, '__storage__', {})
    
            object.__setattr__(self, '__ident_func__', get_ident)   # 赋值为协程
    
        def __iter__(self):
            return iter(self.__storage__.items())
    
    
    
        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        # { 111 : {'stack':[] },222 : {'stack':[] } }
            except KeyError:
                storage[ident] = {name: value}
    
        def __delattr__(self, name):
            try:
                del self.__storage__[self.__ident_func__()][name]
            except KeyError:
                raise AttributeError(name)
    
    
    _local = Local()    # flask的本地线程功能,类似于本地线程,如果有人创建Local对象并,设置值,每个线程里一份
    _local.stack = []   # _local.stack会调用__setattr__的self.__ident_func__()取唯一标识等
    View Code

    2. 特殊栈

    from flask import session
    
    try:
        from greenlet import getcurrent as get_ident
    except ImportError:
        try:
            from thread import get_ident
        except ImportError:
            from _thread import get_ident # 获取线程的唯一标识 get_ident()
    
    class Local(object):
        __slots__ = ('__storage__', '__ident_func__')
    
        def __init__(self):
            # self.__storage__ = {}
            # self.__ident_func__ = get_ident
            object.__setattr__(self, '__storage__', {})
            object.__setattr__(self, '__ident_func__', get_ident)
    
        def __iter__(self):
            return iter(self.__storage__.items())
    
        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 # { 111:{'stack':[] },222:{'stack':[] }  }
            except KeyError:
                storage[ident] = {name: value}
    
        def __delattr__(self, name):
            try:
                del self.__storage__[self.__ident_func__()][name]
            except KeyError:
                raise AttributeError(name)
    
    _local = Local()
    _local.stack = []
    View Code

    3. 利用flask源码中的stack和local

    from functools import partial
    from flask.globals import LocalStack, LocalProxy
    
    _request_ctx_stack = LocalStack()
    
    class RequestContext(object):
        def __init__(self, environ):
            self.request = environ
    
    
    def _lookup_req_object(name):
        top = _request_ctx_stack.top
        if top is None:
            raise RuntimeError(_request_ctx_stack)
        return getattr(top, name)
    
    
    # 实例化了LocalProxy对象,_lookup_req_object参数传递
    session = LocalProxy(partial(_lookup_req_object, 'session'))
    
    
    """
    local = {
        “标识”: {'stack': [RequestContext(),]}
    }
    """
    _request_ctx_stack.push(RequestContext('c1'))  # 当请求进来时,放入
    
    print(session)  # 获取 RequestContext('c1'), top方法
    print(session)  # 获取 RequestContext('c1'), top方法
    _request_ctx_stack.pop()  # 请求结束pop
    View Code
  • 相关阅读:
    wamp集成环境安装后无法启动的问题
    jquery点击内层的click事件时会触发外层的click事件
    js 控制文本框只能输入数字
    第七届飞思卡尔智能车比赛的赛道边缘提取第一篇博客
    多级菜单,多级下拉列表解决方案(收藏) 西安
    ASP.NET 从Excel文件导入数据到数据库(笔记) 西安
    动态构造地址栏参数 西安
    我觉得我应该要回来了 西安
    SQL Server 无法生成 FRunCM 线程。请查看 SQL Server 错误日志和 Windows 事件日志(转) 西安
    Web.Config 分析 西安
  • 原文地址:https://www.cnblogs.com/yunweixiaoxuesheng/p/8418135.html
Copyright © 2020-2023  润新知