Flask上下文管理

上下文管理

  threading.local:为每一个线程开辟一个单独的内存空间来保存自己的值

import threading

# class Foo():
#     def __init__(self):
#         self.name=0
#
# local_value=Foo()

local_value=threading.local()
def func(num):
    local_value.name=num
    import time
    time.sleep(1)
    print(local_value.name,threading.current_thread().name)

for i in range(20):
    th=threading.Thread(target=func,args=(i,),name='线程%s'%i)
    th.start()
示例

  request:

      情况一:单线程和单进程的情况下不会有问题,应为自己使用为完毕过后,就会自动的清空request。

      情况二:单进程和多线程,threading.local对象

      情况三:但进程和单线程多个协程,theading.local对象就会出问题。

 解决方法:

    以后不支持协程:就可以使用内置的threading.local对象

    支持协程:自定义类似threading.local对象的功能支持协程

    _thread.get_ident:获取线程的一个唯一标识

    greenlet.getcurrent:获取协程额唯一标识

 自定义(协程)

"""
{
   1368:{}
}
"""

import threading
try:
    from greenlet import getcurrent as get_ident  # 协程
except ImportError:
    try:
        from thread import get_ident
    except ImportError:
       from _thread import get_ident  # 线程

class Local():
    def __init__(self):
        self.storage={}
        self.get_ident=get_ident

    def set(self,k,v):
        ident=self.get_ident()
        origin=self.storage.get(ident)
        if not origin:
            origin={k:v}
        else:
            origin[k]=v
        self.storage[ident]=origin

    def get(self,k):
        ident=self.get_ident()
        origin=self.storage.get(ident)
        if not origin:
            return None
        return origin.get(k,None)

local_values=Local()

def task(num):
    local_values.set('name',num)
    import time
    time.sleep(1)
    print(local_values.get('name'),threading.current_thread().name)

for i in range(20):
    th=threading.Thread(target=task,args=(i,),name='线程%s'%i)
    th.start()
示例
class Foo(object):
    def __init__(self):
        object.__setattr__(self,'storage',{})

    def __setattr__(self, key, value):
        self.storage={'k1:v1'}
        print(key,value)

    def __getattr__(self, item):
        print(item)
        return 'df'

obj=Foo()
# obj.x = 123

print(obj)
反射示例
"""
{
   1368:{}
}
"""
import flask.globals
import threading
try:
    from greenlet import getcurrent as get_ident  # 协程
except ImportError:
    try:
        from thread import get_ident
    except ImportError:
       from _thread import get_ident  # 线程

class Local():
    def __init__(self):
        object.__setattr__(self,'__storage__',{})
        object.__setattr__(self,'__ident_func__',get_ident)

    def __setattr__(self, name, value):
        ident=self.__ident_func__()
        storage=self.__storage__
        try:
            storage[ident][name]=value
        except KeyError:
            storage[ident]={name:value}
    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)


    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_finc__()][name]
        except KeyError:
            raise AttributeError(name)

local_values=Local()

def task(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=task,args=(i,),name='线程%s'%i)
    th.start()
自定义支持协程的flask:使用反射实现

  偏函数:functools模块

   functools.partial:创建一个新的函数,主要是为了给原函数传入参数

 import functools

 def func(a1,a2):
     print(a1+a2)


 new_func=functools.partial(func,12)  #
 new_func(21)

  面向对象的__add__方法补充:当把面向对象中的所有__函数__实现时,对象做任何操作时,都会执行其中对应的方法。

  __add__:谁在前面就会调用谁的__add__方法

 class Foo():
     def __init__(self,num):
         self.num=num
     def __add__(self,other):
         return Foo(self.num+other.num)
 obj1=Foo(11)
 obj2=Foo(22)
 v=obj1+obj2
 print(v)

 拼接列表中的值:

   itertools.chain:传入函数,产生一个新的函数对象的列表,主要帮助我们做列表元素的拼接

 

#####################示例##################
from itertools import chain

 def a1(x):
     return x+1

 func_list=[a1,lambda x:x+1]

 def a2(y):
     return y+10

 func_list_1=chain([a2],func_list)
 for func in func_list_1:
     print(func)

 

#示例二
 v1=[1,2,3]
 v2=[4,5,6]

 for l in chain(v1,v2):  # chain 主要是做列表的拼接
     print(l)

 

  Flask 上下文管理

- 与django相比是两种不同的实现方式。
            - django/tornado是通过传参数形式
            - flask是通过上下文管理
            两种都可以实现,只不过试下方式不一样。
            - 上下文管理:
                - threading.local/Local类,其中创建了一个字典{greelet做唯一标识:存数据} 保证数据隔离
                - 请求进来:
                    - 请求相关所有数据封装到了RequestContext中。
                    - 再讲RequestContext对象添加到Local中(通过LocalStack将对象添加到Local对象中)
                - 使用,调用request
                    - 调用此类方法 request.method、print(request)、request+xxx 会执行LocalProxy中对应的方法
                    - 函数
                    - 通过LocalStack去Local中获取值。
                - 请求终止
                    - 通过LocalStack的pop方法 Local中将值异常。

  请求上下文:

  request:是LocalProxy对象

   这个对象实例化事传入了一个函数,还有一个request参数
   以后执行函数partail(_Lookup_req_object,'request')时,自动传递request参数
   目标:去Local中获取ctx,然后再在ctx中获取request
   ctx.push:里面做的事,将ctx通过LocalStack添加到Local中

  session:经过push之后,session里面已经有值了,seif.session帮助我获取session信息

 应用上下文:

  AppContext里面封装了app对象
  app.context:创建了app对象
  app.app_ctx_global_class:相当于一个全局变量

  app和g

  g:每个请求周期都会创建一个用于在请求周期中传递值的一个容器。只有一次生命周期可用,因为一次请求周期后第二次就会重新创建
print(g):执行g的实例对象

 请求到来 ,有人来访问:

 

# 将请求相关的数据environ封装到了RequestContext对象中
                # 再讲对象封装到local中(每个线程/每个协程独立空间存储)
                # ctx.app # 当前APP的名称
                # ctx.request # Request对象(封装请求相关东西)
                # ctx.session # 空
                _request_ctx_stack.local = {
                    唯一标识:{
                        "stack":[ctx, ]
                    },
                    唯一标识:{
                        "stack":[ctx, ]
                    },
                }
                
                
                # app_ctx = AppContext对象
                # app_ctx.app
                # app_ctx.g
                    
                _app_ctx_stack.local = {
                    唯一标识:{
                        "stack":[app_ctx, ]
                    },
                    唯一标识:{
                        "stack":[app_ctx, ]
                    },
                }

  使用:print打印request,session.g,current_app的时候,都会执行他们相对应的对象的__str__
他们之间不同的是偏函数不一样

from flask import request,session,g,current_app
                    
                    print(request,session,g,current_app)
                    
                    都会执行相应LocalProxy对象的 __str__
                    
                    current_app = LocalProxy(_find_app)
                        request = LocalProxy(partial(_lookup_req_object, 'request'))
                        session = LocalProxy(partial(_lookup_req_object, 'session'))
                        
                        current_app = LocalProxy(_find_app)
                        g = LocalProxy(partial(_lookup_app_object, 'g'))
示例

 终止,全部pop

 武sir飙车时间:

  如果他是多线程的时候是如何实现的。

司机飙车

  flask的local中保存数据时,使用列表创建出来的栈。为什么用栈?

- 如果写web程序,web运行环境;栈中永远保存1条数据(可以不用栈)。
                   - 写脚本获取app信息时,可能存在app上下文嵌套关系。
                        from flask import Flask,current_app,globals,_app_ctx_stack

                        app1 = Flask('app01')
                        app1.debug = False # 用户/密码/邮箱
                        # app_ctx = AppContext(self):
                        # app_ctx.app
                        # app_ctx.g

                        app2 = Flask('app02')
                        app2.debug = True # 用户/密码/邮箱
                        # app_ctx = AppContext(self):
                        # app_ctx.app
                        # app_ctx.g



                        with app1.app_context():# __enter__方法 -> push -> app_ctx添加到_app_ctx_stack.local
                            # {<greenlet.greenlet object at 0x00000000036E2340>: {'stack': [<flask.ctx.AppContext object at 0x00000000037CA438>]}}
                            print(_app_ctx_stack._local.__storage__)
                            print(current_app.config['DEBUG'])

                            with app2.app_context():
                                # {<greenlet.greenlet object at 0x00000000036E2340>: {'stack': [<flask.ctx.AppContext object at 0x00000000037CA438> ]}}
                                print(_app_ctx_stack._local.__storage__)
                                print(current_app.config['DEBUG'])

                            print(current_app.config['DEBUG'])
司机飙车

 多app应用:不仅可以使用蓝图进行分发,还可以使用app进行分发
  DispatchMiddleware:可以进行路由分发。
  在这里面没有app.run,直接可以run_simple启动

from werkzeug.wsgi import DispatcherMiddleware
            from werkzeug.serving import run_simple
            from flask import Flask, current_app

            app1 = Flask('app01')

            app2 = Flask('app02')



            @app1.route('/index')
            def index():
                return "app01"


            @app2.route('/index2')
            def index2():
                return "app2"

            # http://www.oldboyedu.com/index
            # http://www.oldboyedu.com/sec/index2
            dm = DispatcherMiddleware(app1, {
                '/sec': app2,
            })

            if __name__ == "__main__":
                run_simple('localhost', 5000, dm)
示例

 问题:web访问多app,上线问管理是如何实现的

  请求进来,为栈添加的还是一个值

 问题:为什么使用栈,离线脚本

  如果写web程序,或者web运行环境:栈中永远保存一条数据(这个可以不使用栈)
  写脚本获取app信息的时候,可能会存在上下文嵌套关系。这时有可能要用到栈

 问题:Web访问多app应用时,上下文管理是如何实现?

 补充:

 遇到with就会执行__enter__方法,这个方法返回值,as后面那个值就是返回值
 当指执行完毕之后 ,自动调用类的__exit__方法
from flask import Flask,current_app,globals,_app_ctx_stack

app1 = Flask('app01')
app1.debug = False # 用户/密码/邮箱
# app_ctx = AppContext(self):
# app_ctx.app
# app_ctx.g

app2 = Flask('app02')
app2.debug = True # 用户/密码/邮箱
# app_ctx = AppContext(self):
# app_ctx.app
# app_ctx.g

with app1.app_context():  # # __enter__方法 -> push -> app_ctx添加到_app_ctx_stack.local
    print(_app_ctx_stack._local.__storage__)
    print(current_app.config['DEBUG'])
    with app1.app_context():
        print(_app_ctx_stack._local.__storage__)
        print(current_app.config['DEBUG'])
    print(current_app.config['DEBUG'])


with app1.app_context():
    print(_app_ctx_stack._local.__storage__)
    print(current_app.config['DEBUG'])
with app1.app_context():
    print(_app_ctx_stack._local.__storage__)
    print(current_app.config['DEBUG'])
示例

二 数据库连接池

 django的常用数据库:

  ORM:django默认的数据库

  pymysql模块:导入mysql数据库,python2和python3版本都有这个模块

  MySQLdb:一样,也是导入mysql数据库,这个模块只有python2版本才有

 flask/其他:

  pymysql:导mysql数据库

  MySQLdb:导入mysql数据库

 SQLAchemy:

  是ORM的一款框架,可以导入mysql数据库(pymysql / MySQLdb)

 原生SQL:

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    import pymysql
    CONN = pymysql.connect(host='127.0.0.1',
                           port=3306,
                           user='root',
                           password='123',
                           database='pooldb',
                           charset='utf8')

    cursor = CONN.cursor()
    cursor.execute('select * from tb1')
    result = cursor.fetchall()
    cursor.close()

    print(result)

    return "Hello World"

if __name__ == '__main__':
    app.run()
示例
from flask import Flask

app = Flask(__name__)
import pymysql
CONN = pymysql.connect(host='127.0.0.1',
                           port=3306,
                           user='root',
                           password='123',
                           database='pooldb',
                           charset='utf8')

@app.route("/")
def hello():
    cursor = CONN.cursor()
    cursor.execute('select * from tb1')
    result = cursor.fetchall()
    cursor.close()
    print(result)
    return "Hello World"

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




######加锁#########################
from flask import Flask
import threading
app = Flask(__name__)
import pymysql
CONN = pymysql.connect(host='127.0.0.1',
                           port=3306,
                           user='root',
                           password='123',
                           database='pooldb',
                           charset='utf8')

@app.route("/")
def hello():
    with threading.Lock():
        cursor = CONN.cursor()
        cursor.execute('select * from tb1')
        result = cursor.fetchall()
        cursor.close()

        print(result)

    return "Hello World"

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


 解决问题:

  不能为每一个用户创建一个链接

  创建一定数量的连接池,只要连接池有空的,才会进来,不然就会等着。

 使用UBDtils模块:

  下载网站:https://pypi.python.org/pypi/DBUtils

   pip install UBDtils

  如果安装到虚拟环境下面,需要先切换到虚拟环境下面

  使用:

   模式一:

    为每一个线程创建一个链接,即使县城调用close方法,也不会关闭掉

    占用连接池不放,浪费了连接池

 

import pymysql
from DBUtils.PersistentDB import PersistentDB

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 = POOL.connection(shareable=False)
    cursor = conn.cursor()
    cursor.execute('select * from tb1')
    result = cursor.fetchall()
    cursor.close()
    conn.close()

func()
示例

 模式二:
   设置最大限制创建连接池的数量,进来一个线程,就创建一个连接池

   如果超过连接池的限制数量,就会在那里等着有空的连接池释放出来才能够下一个线程链接进去

 

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='123',
    database='pooldb',
    charset='utf8'
)


def func():
    # 检测当前正在运行连接数的是否小于最大链接数,如果不小于则:等待或报raise TooManyConnections异常
    # 否则
    # 则优先去初始化时创建的链接中获取链接 SteadyDBConnection。
    # 然后将SteadyDBConnection对象封装到PooledDedicatedDBConnection中并返回。
    # 如果最开始创建的链接没有链接,则去创建一个SteadyDBConnection对象,再封装到PooledDedicatedDBConnection中并返回。
    # 一旦关闭链接后,连接就返回到连接池让后续线程继续使用。
    conn = POOL.connection()

    # print(th, '链接被拿走了', conn1._con)
    # print(th, '池子里目前有', pool._idle_cache, '\r\n')

    cursor = conn.cursor()
    cursor.execute('select * from tb1')
    result = cursor.fetchall()
    conn.close()


func()
示例

 工作实用

import pymysql
                from DBUtils.PooledDB import PooledDB
                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='123',
                    database='pooldb',
                    charset='utf8'
                )

                """
                class SQLHelper(object):
                    
                    @staticmethod
                    def fetch_one(sql,args):
                        conn = POOL.connection()
                        cursor = conn.cursor()
                        cursor.execute(sql, args)
                        result = cursor.fetchone()
                        conn.close()
                        return result

                    @staticmethod
                    def fetch_all(self,sql,args):
                        conn = POOL.connection()
                        cursor = conn.cursor()
                        cursor.execute(sql, args)
                        result = cursor.fetchone()
                        conn.close()
                        return result
                    
                # 以后使用:
                result = SQLHelper.fetch_one('select * from xxx',[])
                print(result)
                """
示例

三、信号

 什么是信号:signal 一种处理异步时间的方法。信号是POSIX系统的信号,由硬件或软件触发,再有操作系统内核发给应用程序的中断形式。POSIX由一系列的信号集。

 什么是信号量:semaphore 一种进程同步的机制。信号量是POSIX进程间通信的工具,在它上面定义了一系列操作原语,简单地讲它可以在进程间进行通信。

 flask自己没有信号,要依赖blinker这个模块 安装 pip install blinker
 内置信号:

request_started = _signals.signal('request-started')                # 请求到来前执行
request_finished = _signals.signal('request-finished')              # 请求结束后执行
 
before_render_template = _signals.signal('before-render-template')  # 模板渲染前执行
template_rendered = _signals.signal('template-rendered')            # 模板渲染后执行
 
got_request_exception = _signals.signal('got-request-exception')    # 请求执行出现异常时执行
 
request_tearing_down = _signals.signal('request-tearing-down')      # 请求执行完毕后自动执行(无论成功与否)
appcontext_tearing_down = _signals.signal('appcontext-tearing-down')# 请求上下文执行完毕后自动执行(无论成功与否)
 
appcontext_pushed = _signals.signal('appcontext-pushed')            # 请求上下文push时执行
appcontext_popped = _signals.signal('appcontext-popped')            # 请求上下文pop时执行
message_flashed = _signals.signal('message-flashed')                # 调用flask在其中添加数据时,自动触发
示例

 信号放哪里了:放在了signals文件里面
 signals.request_started.conncet(函数名) :注册函数
 等待请求的到来
 在视图函数执行之前触法信号的
 是怎么触发信号的?
  signals.request_started.send:方法触发信号
  send是在full_dispatch_request方法中执行的

 1 a. before_first_request
 2  b. 触发 request_started 信号
 3  c. before_request
 4  d. 模板渲染
 5    渲染前的信号 before_render_template.send(app, template=template, context=context)
 6     rv = template.render(context) # 模板渲染
 7    渲染后的信号 template_rendered.send(app, template=template, context=context)
 8  e. after_request
 9  f. session.save_session()
10  g. 触发 request_finished信号
11 
12  如果上述过程出错:
13    触发错误处理信号 got_request_exception.send(self, exception=e)
14    
15  h. 触发信号 request_tearing_down
16       
17  由信号引发的源码流程:找扩展点
信号源码流程

 

posted @ 2018-03-28 16:59  可造之猿  阅读(125)  评论(0)    收藏  举报