• flask框架


    flask

    django是个大而全的框架,flask是一个轻量级的框架

    django内部为我们提供了非常多的组件,orm/session/cookie/admin/form/modelform/路由/视图/模块/中间件/分页/auth/contenttype/缓存/信号/多数据库

    flask框架本身没有太多的功能,路由视图模板(jinjia2)/中间件 ,第三方组件非常齐全。

    注意事项:django的请求处理是封装传递。

    flask的请求是利用上下文管理来实现的(放到一个地方,在去这个地方取)

    最大区别处理机制不同:
    django:通过传参的方式
    flask:通过上下文管理方式实现
    

    flask快速使用

    学习博客:https://www.cnblogs.com/wupeiqi/articles/7552008.html

    安装

    pip3 install flask
    

    wgsi

    django和flask内部没有实现socket,而是wsgi实现的。
    wsgi是web服务的网关接口,是一个协议。实现他的协议有wsgiref、werkzurg、uwsgi(多并发,部署django项目用)
    

    依赖wsgi Werkzeug

    from werkzeug.serving import run_simple
    
    def func(environ,start_response):
    	print('请求来了')
    	pass
    if __name__ == '__main__':
    	run_simple('127.0.0.1',5000,func)
    
    from werkzeug.serving import run_simple
    class Flask(object):
    	def __call__(self,environ,start_response)
    		return 'xx'
    app=Flask()
    
    if __name__ == '__main__':
    	run_simple('127.0.0.1',5000,app) #访问游览器,执行对象app(),会执行类中的call方法
    
    from werkzeug.serving import run_simple
    
    class Flask(object):
    	def __call__(self,environ,start_response)
    		return 'xx'
    	def run(self):
    			run_simple('127.0.0.1',5000,app) #访问游览器,执行对象app(),会执行类中的call方法
    app=Flask()
    
    if __name__ == '__main__':
    	app.run()
    

    app.run()的执行流程:https://www.h5w3.com/13717.html

    快速使用flask

    from flask import Flask
    
    app = Flask(__name__)
    # print(__name__) #app 文件名
    
    @app.route('/')
    def hello_world():
        return 'Hello World!'
    
    @app.route('/index')
    def index():
        return 'index!'
    
    if __name__ == '__main__':
        app.run()
    

    总结:

    • flask框架是基于werkzeug的wsgi实现的,自己没有wgsi
    • 用户请求一但到来,就会执行app的call方法

    基本使用:

    # -*- coding: utf-8 -*-
    
    
    
    from flask import Flask,request,redirect,render_template
    
    
    app=Flask(__name__)
    # print(__name__) #s1
    
    
    data_dict = {
        1:{'name':'xx','age':77},
        2:{'name':'zz','age':778},
        3:{'name':'zzx','age':999},
    }
    
    
    #装饰器
    @app.route('/login',methods=['GET','POST'])
    def login():
        if request.method == 'GET':
            return render_template('login.html')
    
        #post请求取值
        user = request.form.get('user')
        pwd = request.form.get('pwd')
        if user  == 'zz' and pwd == '123':
            return redirect('/index')  #url
    
        else:
            error = 'error'
    
        return render_template('login.html',error=error)
    
    @app.route('/index',endpoint='idx') #idx url别名
    def index():
        return render_template('index.html',data_dict=data_dict)
    
    @app.route('/edit')
    def edit():
        #get请求取值
        nid = request.args.get('nid')
        info = data_dict[int(nid)]
        return '修改'
    
    @app.route('/del/<int:nid>')  #从url上取id
    def delete(nid):
        print(nid)
        del data_dict[nid]
        return '删除'
    
    if __name__ == '__main__':
        app.run()
    

    总结:

    1.flask 路由

    @app.route('/login',methods=['GET','POST'])
    def login():
    	pass
    

    2.路由的参数

    @app.route('/login',methods=['GET','POST'],endpoint='login') 
    def login():
    	pass
    注意:endpoint:url别名,不能重名,默认为函数名
    

    3.动态路由

    @app.route('/edit')
    def edit():
        #get请求取值
        nid = request.args.get('nid') #前端模板url /xxx?nid=2 
        info = data_dict[int(nid)]
        return '修改'
    	
    @app.route('/del/<int:nid>')  #前端模板url /xxx/2 
    def delete(nid):
        print(nid)
        del data_dict[nid]
        return '删除'
    
    @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'])
    

    4.后端获取提交的数据

    get请求: nid = request.args.get('nid') 
    post请求:pwd = request.form.get('pwd')
    

    5.返回数据

    return render_template('login.html')
    return redirect('/index')  #是返回一个url路径
    return redirect(url_for('idx')) #返回url别名
    return jsonify({}) #相当于djanog 的JsonResponse
    return '修改' 
    

    6.模板处理

    {{x}}
    
    {% for item in data_dict %}
    	{{item.name}}
    	{{ item.get('name') }}
    	{{ item['name'] }}
    {% endfor %}
    #语法和python非常接近,这一点比django好
    

    7.注释

    ctrl + l 
    {#get请求传参#}
    

    8.session 保存用户会话信息

    flask的session是保存在游览器的cookie。加密存储

    from flask import Flask,session
    app=Flask(__name__) 
    app.secret_key = 'xsadsagadsfg' #需要在app中设置secret_key
    session['xx']=zzz #就可以设置session
    获取
    session.get('xx')
    

    9.装饰器实现用户认证

     import functools
     def auth(func):
     	@functools.wrap(func) #让参数名为调用的函数,而不是inner
     	def inner(*args,**kwargs):
     		username = session.get('xx')
     		if not username:
     			return redirect(url_for('login'))
    		return func(*args,**kwargs) 	
    	return inner 
    @app.route('/index',endpoint='idx')
    @auth  #装饰器上下往上执行,auth装饰器中必须有functools,他将
    def index():
    	pass
    print(index.__name__) #结果为index
    

    蓝图(blue print)

    帮助我们可以将很多的业务拆分,创建多个py文件,把各个功能放在不同蓝图中,最后将蓝图注册到flask对象中。

    构建目录结构

    manage.py
    # -*- coding: utf-8 -*-
    
    from blue_print_flask import create_app
    app = create_app()
    if __name__ == '__main__':
        app.run()
    
    __init__.py
    # -*- coding: utf-8 -*-
    
    from flask import Flask
    from .views.my import xmy
    from .views.wy import xwy
    
    def create_app():
        app = Flask(__name__)
        app.secret_key = 'sdasfgasf'
    
        @app.route('/index')
        def index():
            return 'index'
    
        #注册 和蓝图创建关系
        app.register_blueprint(xmy,url_prefix='/web')  #url_prefix 前缀 访问/web/f1
        app.register_blueprint(xwy,url_prefix='/admin')
    
        return app
    
    my.py
    # -*- coding: utf-8 -*-
    
    from flask import Blueprint #引入蓝图
    xmy=Blueprint('my',__name__)
    
    @xmy.route('/f1')
    def f1():
        return 'f1'
    
    @xmy.route('/f2')
    def f2():
        return 'f2'
    

    面试题:

    django的app和flask的蓝图有什么区别?

    没什么太大区别。django在settings中注册,flask在create_app()函数中注册app

    http和https区别

    htpp:端口80
    https:端口443
    
    http的数据是基于明文传输。
    https的数据是基于密文传输。 
    对称加密:
    	客户端向服务器发送一条信息,首先客户端会采用已知的算法对信息进行加密,比如MD5或者Base64加密,接收端对加密的信息进行解密的时候需要用到密钥,中间会传递密钥,(加密和解密的密钥是同一个),密钥在传输中间是被加密的。
    非对称加密:公钥,私钥。私钥在自己服务器。
    	“非对称加密”使用的时候有两把锁,一把叫做“私有密钥”,一把是“公开密钥”,使用非对象加密的加密方式的时候,服务器首先告诉客户端按照自己给定的公开密钥进行加密处理,客户端按照公开密钥加密以后,服务器接受到信息再通过自己的私有密钥进行解密,这样做的好处就是解密的钥匙根本就不会进行传输,因此也就避免了被挟持的风险。
    证书秘钥加密:
    	在上面我们讲了非对称加密的缺点,其中第一个就是公钥很可能存在被挟持的情况,无法保证客户端收到的公开密钥就是服务器发行的公开密钥。此时就引出了公开密钥证书机制。数字证书认证机构是客户端与服务器都可信赖的第三方机构。
    

    cookie和session

    cookie:保存在游览器上

    session:是基于cookie的一种机制,将用户会话信息保存在服务器端

    token:令牌

    jwt:json web token

    开放封闭原则

    对源码封闭 对配置开放

    什么是接口?

    -interface类型,java/c#语言中才有,用于约束实现了该接口的类中必须有的某些方法。
    -api也可以称为一个接口,url访问,drf,restful风格
    

    抽象方法/抽象类

    具有约束的功能,让子类继承的功能。python中有abc模块实现。 
    但我们一般用raise NotmplementedError 来约束
    

    flask链接数据库分为两种:

    • sqlalchemy 一个orm框架
    pip install flask-sqlalchemy
    
    • sqlhelper 写原生sql

    数据库连接池dbutils (SQLHelper)

    参考 https://www.cnblogs.com/wupeiqi/articles/8184686.html

    并发情况下 最好用数据库连接池
    
    安装
    pip3 install dbutils
    pip3 install pymysql
    
    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, '
    ')
    
       cursor = conn.cursor()
       cursor.execute('select * from tb1')
       result = cursor.fetchall()
       #关闭链接,连接就返回到连接池让后续线程继续使用
       conn.close() 
    
    
    func()
    

    flask中的sqlhelper

    # -*- coding: utf-8 -*-
    from DBUtils.PooledDB import PooledDB
    import pymysql
    
    
    class SqlHelper(object):
        def __init__(self):
            self.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 open(self):
            conn = self.pool.connection()
            cursor = conn.cursor()
            return conn, cursor
    
        def close(self, cursor, conn):
            cursor.close()
            conn.close()
    
        def fetchall(self, sql, *args):
            '''获取所有数据'''
            conn, cursor = self.open()
            cursor.execute(sql, args)
            result = cursor.fetchall()
            self.close(cursor, conn)
            return result
    
        def fetchone(self, sql, *args):
            '''获取所有数据'''
            conn, cursor = self.open()
            cursor.execute(sql, args)
            result = cursor.fetchone()
            self.close(cursor, conn)
            return result
    
    
    db = SqlHelper()
    
    #在flask 的视图中
    from sqlhelper import db  #单例模式
    @app.route('/index',endpoint='idx') #idx url别名
    def index():
    	db.fetchall('select * from xx where name =%s,'zz')
    	
    	‘’‘
    	或者自己写
    	db.open()
    	....sql逻辑
    	db.cloese()
    	’‘’
    
    test.py
    #参数的解释
    def f1(sql,*args,**kwargs):
        print(sql,args,kwargs)
    
    a=f1('xxx','xx','xx',a=1,b=2) 
    print(a)#xxx ('xx', 'xx') {'a': 1, 'b': 2} args返回元组,kwargs返回zi'di'a
    

    知识点:上下文管理机制 (这里是面向对象的上下文管理,但和flask中的上下文管理无关)

    # 面试题
    class Foo(object):
        def do_something(self):
            print('内部执行')      # 第三步
    
    class Context:
        def __enter__(self):
            print('进入')           # 第一步
            return Foo()
        def __exit__(self, exc_type, exc_val, exc_tb):
            print('推出')      # 第四步
    
    with Context() as ctx:   #这里的ctx 就是接受return的返回值
        print('内部执行')      # 第二步
        ctx.do_something()
    #with Context() 会执行对象的__enter__方法,所以ctx就是返回的Foo()对象
    

    跟据flask源码反推wsgi返回值

    #werkzeug自己就能实现返回值
    
    from werkzeug.serving import run_simple
    from werkzeug.wrappers import BaseResponse
    from flask.wrappers import BaseResponse
    
    def func(environ,start_response):
    	print('请求来了')
    	response=BaseResponse('你好')
    	return response(environ,start_response)
    if __name__ == '__main__':
    	run_simple('127.0.0.1',5000,func)
    
    

    静态文件处理

    Flask() 初始化参数    
        def __init__(
            self,
            import_name, #app名
            static_url_path=None, #静态文件路径,默认为 /static 
            static_folder="static", #静态文件名
            static_host=None,
            host_matching=False,
            subdomain_matching=False,
            template_folder="templates",
            instance_path=None,
            instance_relative_config=False,
            root_path=None,
        )
    
    html
    推荐写法
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <img src="/static/qq.jpg" alt="">
    
    #推荐 这样之后改static_url_path,就不用更改了。会自动找static文件夹
    <img src="{{ url_for('static',filename='qq.jpg') }}" alt="">
    </body>
    </html>
    

    flask配置文件

    - local settings 第一种

    #新见一个config文件夹 里面有settings.py,localsettings.py
    #settings.py
    DB_IP = 'XX'
    S
    #导入localsettings
    try:
    	from .localsettings import * 
    expect ImportError:
    	pass
    
    #localsettings.py  本地自己的配置,在上传git的时候不上传这个,写到.gitignore文件里
    DB_IP = 'XX' 
    
    #app.py
    from flask import Flask
    app=Flask(__name__)
    #加载配置文件
    app.config.from_object('config.settings')  #通过字符串导入改py文件
    #取值
    app.config['DB_IP']
    
    

    第二种 基于类的一种

    #app.py
    from flask import Flask
    app=Flask(__name__)
    #加载配置文件
    app.config.from_object('config.settings.Probsettings')
    
    #settings.py
    class Probsettings():
    	DB_IP ='xx'
    

    路由:

    两种添加路由和视图对应关系的方法:
    def index():
    	return render_template('index.html')
    #添加关系 一般用这个就行
    app.add_url_rule('/index','index',index,methods=["GET","POST"]) #url路径,url别名,视图名
    or
    self.add_url_rule(rule='/index.html', endpoint="index", view_func=index, methods=["GET","POST"])
    or
    app.add_url_rule(rule='/index.html', endpoint="index", view_func=index, methods=["GET","POST"])
    app.view_functions['index'] = index
    
    
    #一般用这种
    @app.route('/login')
    def index():
    	return render_template('index.html')
    

    路由加载的源码流程

    -将url和函数打包为rule对象
    -将rule对象添加到map对象中
    -将map对象加入app对象中
    	- app.url_map=map对象 
    

    支持这几种:

    @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'])
    

    自定义路由:

            from flask import Flask, views, url_for
                from werkzeug.routing import BaseConverter
    
                app = Flask(import_name=__name__)
                
                class RegexConverter(BaseConverter):
                    """
                    自定义URL匹配正则表达式
                    """
                    def __init__(self, map, regex):
                        super(RegexConverter, self).__init__(map)
                        self.regex = regex
    
                    def to_python(self, value):
                        """
                        路由匹配时,匹配成功后传递给视图函数中参数的值
                        :param value: 
                        :return: 
                        """
                        return int(value)
    
                    def to_url(self, value):
                        """
                        使用url_for反向生成URL时,传递的参数经过该方法处理,返回的值用于生成URL中的参数
                        :param value: 
                        :return: 
                        """
                        val = super(RegexConverter, self).to_url(value)
                        return val
    
                # 添加到flask中
                app.url_map.converters['regex'] = RegexConverter
    
    
                @app.route('/index/<regex("d+"):nid>')
                def index(nid):
                    print(url_for('index', nid='888'))
                    return 'Index'
    
    
                if __name__ == '__main__':
                    app.run()
    

    视图cbv:

    CBV: 返回一个闭包函数view

    from flask import views
    
            def auth(func):
                def inner(*args, **kwargs):
                    print('before')
                    result = func(*args, **kwargs)
                    print('after')
                    return result
    
            return inner
        
        class IndexView(views.View):
                methods = ['GET']
                decorators = [auth, ]
    
                def dispatch_request(self):
                    print('Index')
                    return 'Index!'
    
            app.add_url_rule('/index', view_func=IndexView.as_view(name='index'))  # name=endpoint
            或
            class IndexView(views.MethodView):
                methods = ['GET']
                decorators = [auth, ] #装饰器
    
                def get(self):
                    return 'Index.GET'
    
                def post(self):
                    return 'Index.POST'
    
            app.add_url_rule('/index', view_func=IndexView.as_view(name='index'))  # name=endpoint
    

    模板:

    和django类似,可以根据django中的语法学习

    <!DOCTYPE html>
    <html>
    <head lang="en">
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <h1>自定义函数</h1>
        {{ww()|safe}}  #可以传函数名,前端加()执行函数
    
    </body>
    </html>
    
    from flask import Flask,render_template
    app = Flask(__name__)
     
     
    def wupeiqi():
        return '<h1>Wupeiqi</h1>'
     
    @app.route('/login', methods=['GET', 'POST'])
    def login():
        return render_template('login.html', ww=wupeiqi)
     
    app.run()
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    
        {% macro input(name, type='text', value='') %}
            <input type="{{ type }}" name="{{ name }}" value="{{ value }}">
        {% endmacro %}
    
        {{ input('n1') }}
    
        {% include 'tp.html' %}
    
        <h1>asdf{{ v.k1}}</h1>
    </body>
    </html>
    

    定义全局模板方法

    加装饰器让函数在所有模板中都可使用(全局)
    @app.template_global()
    def func(arg):
    	return 'xx' + arg
    #前端模板
    {{ func("hhh") }} #返回xxhhh
    
    @app.template.filter()
    def func(arg,name):
    	return 'xx' + arg +name 
    #前端模板
    {{ "hhh"|func("sun") }} #返回xxhhhsun
    

    注意:在蓝图中注册时,应用范围只在本蓝图中。(@xx.app_template_global())

    特殊装饰器(中间件):

    @app.before_request
    def f1():
    	# if request.path == '/login': #可做白名单或者是登录session验证
    	#	return  #想当return None,跳过中间件
    	
    	print('f1')
    
    @app.after_request  #这里必须返回reponse。和django中间件中process_response中的return response
    def f10(response):
    	print('f10')
    	return response
    	
    @app.route('/index')
    def index():
    	print('index')
    #打印结果
    f1
    index
    f10
    

    多个装饰器(中间件):

    @app.before_request
    def f1():
    	print('f1')
    	
    @app.before_request
    def f2():
    	print('f2')
    
    @app.after_request  #这里必须返回reponse。和django中间件中process_response中的return response
    def f10(response):
    	print('f10')
    	return response
    	
    def f11(response):
    	print('f11')
    	return response
    
    @app.route('/index')
    def index():
    	print('index')
    #打印结果
    f1
    f2
    index
    f11
    f10
    #flask实现逻辑:
    会把before_request的函数加到一个列表中for循环执行。
    把after_request的函数加到列表中,进行revese反转在执行。
    

    注意:在蓝图中定义,作用域只在本蓝图

    小细节其他写法了解:

    (装饰器本质,将下面紧挨着的函数当参数传入)

    @app.before_request
    def f2():
    	print('f2')
    	
    def f23():
    	print('f23')
    #f23=app.before_request(f23)
    app.before_request(f23) #这样写也可以
    

    threading.local的作用 (flask中没有)

    上面案例:如果不用threading.local,结果是都是3(最后一个线程完成的结果)。

    为每个线程都会开辟一块空间,让你进行存取值。

    栈和面向对象attr:

    栈:
    list = []
    list.pop() # 后进先出,可以理解装弹夹
    
    # -*- coding:utf-8 -*-
    class Student:
        def __getattr__(self, item):
            return item + ' is not exits'
     
        def __setattr__(self, key, value):
            self.__dict__[key] = value
     
        def __getitem__(self, item):
            return self.__dict__[item]
     
        def __setitem__(self, key, value):
            self.__dict__[key] = value
     
     
    s = Student()
    print(s.name)  # 调用__getattr__方法 输出'name is not exits'
    s.age = 1  # 调用__setattr__ 方法
    print(s.age)  # 输出 1
    print(s['age'])  # 调用 __getitem__方法 输出1
    s['name'] = 'tom'  # 调用 __setitem__ 方法
    
    

    线程的唯一标识:

    import threading
    
    from threading import get_ident
    
    def task(): 
        ident =get_ident() #获取每一个线程的标识
        print(ident)  
    
    for i in range(20):
        t=threading.Thread(target=task)
        t.start()
    

    自定义threading local :

    每个线程都会维护一个地方去取存值。

    class Local(object):
    
        def __init__(self):
            object.__setattr__(self, 'storage', {})  #self.storage={} 防止递归
    
        def __setattr__(self, key, value):
            ident = threading.get_ident()
            if ident in self.storage:
                self.storage[ident][key] = value
            else:
                self.storage[ident] = {key:value}
    
        def __getattr__(self, item):
            ident = threading.get_ident()
            if ident not in self.storage:
                return  #返回None
            return  self.storage[ident].get(item)
    
    local=Local()
    
    def task(arg):
        local.x1 = arg  #会调用setattr赋值
        print(local.x1) #调用getattr
    
    for i in range(5):
        t=threading.Thread(target=task,args=(i,))
        t.start()
    #结果 0 1 2 3 4
    

    高级点的threading local

    数据维护成一个栈 #列表 [ ]先进后出

    import threading
    storage = {
    	111:{'x1':[1,]},
    	222:{'x1':[2,]},
    }
    
    class Local(object):
    	
        def __init__(self):
            object.__setattr__(self, 'storage', {})  # self.storage={} 防止递归
    
        def __setattr__(self, key, value):
            ident = threading.get_ident()
            if ident in self.storage:
                self.storage[ident][key].append(value)
            else:
                self.storage[ident] = {key:[value,]}
    
        def __getattr__(self, item):
            ident = threading.get_ident()
            if ident not in self.storage:
                return  # 返回None
            return self.storage[ident][item][-1]  #取最后一个  栈顶
    
    local = Local()
    def task(arg):
        local.x1 = arg  # 会调用setattr赋值
        print(local.x1)  # 调用getattr
    
    for i in range(5):
        t = threading.Thread(target=task, args=(i,))
        t.start()
    

    flask源码源于local的实现(local,localstack)

    local类:

    from flask import globals
    _request_ctx_stack = LocalStack() #从这里面找
    
    class Local(object):
        __slots__ = ("__storage__", "__ident_func__")
    
        def __init__(self):
            object.__setattr__(self, "__storage__", {})
            object.__setattr__(self, "__ident_func__", get_ident)
    
        def __iter__(self):
            return iter(self.__storage__.items())
    
        def __call__(self, proxy):
            """Create a proxy for a name."""
            return LocalProxy(self, proxy)
    
        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
            except KeyError:
                storage[ident] = {name: value}
    
        def __delattr__(self, name):
            try:
                del self.__storage__[self.__ident_func__()][name]
            except KeyError:
                raise AttributeError(name)
    

    localstack类:

    class LocalStack(object):
        """This class works similar to a :class:`Local` but keeps a stack
        of objects instead.  This is best explained with an example::
            >>> ls = LocalStack()
            >>> ls.push(42)
            >>> ls.top
            42
            >>> ls.push(23)
            >>> ls.top
            23
            >>> ls.pop()
            23
            >>> ls.top
            42
        .. versionadded:: 0.6.1
        """
    
        def __init__(self):
            self._local = Local()
    
        def __release_local__(self):
            self._local.__release_local__()
    
        def _get__ident_func__(self):
            return self._local.__ident_func__
    
        def _set__ident_func__(self, value):
            object.__setattr__(self._local, "__ident_func__", value)
    
        __ident_func__ = property(_get__ident_func__, _set__ident_func__)
        del _get__ident_func__, _set__ident_func__
    
        def __call__(self):
            def _lookup():
                rv = self.top
                if rv is None:
                    raise RuntimeError("object unbound")
                return rv
    
            return LocalProxy(_lookup)
    
        def push(self, obj):
            """Pushes a new item to the stack"""
            rv = getattr(self._local, "stack", None)
            if rv is None:
                self._local.stack = rv = []
            rv.append(obj)
            return rv
    
        def pop(self):
            """Removes the topmost item from the stack, will return the
            old value or `None` if the stack was already empty.
            """
            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
    

    总结

    在flask中有一个Local类,他和threading.local的功能一样,为每个线程开辟空间进行存取值。
    他们两个的内部实现机制,内部维护一个字典,以线程(协程id)为key,进行数据隔离 如:

    __storage__={
    	1211:{'k1':123},	
    }
    obj =Local()
    obj.k1 =123  #找到他自己的线程id
    

    在flask中有一个LocalStack类,他内部会依赖local对象,local对象负责存储数据,localstack对象用于将local中的值维护成一个栈

    __storage__={
    	1211:{'stack':[]},	
    }
    obj = LocalStack()
    obj.push('k1') #往列表中添加值
    obj.top #取栈顶的值 [-1]
    obj.pop() #pop值,当列表中只有一个值时,会将这个栈销毁del
    

    在flask源码中只有两个localstack对象(单例模式实现)

    from flask import globals
    _request_ctx_stack = LocalStack()  #不管导入多少次,都只有这一个_request_ctx_stack对象
    _app_ctx_stack = LocalStack()
    
    一个请求过来,分别_request_ctx_stack.push(),_app_ctx_stack.push()
    __storage__={
    	1111:{'stack':[RequestContext(request,sesion),]},	
    	1122:{'stack':[RequestContext(request,sesion),]},	
    }
    
    _request_ctx_stack = LocalStack()
    
    __storage__={
    	1111:{'stack':[AppContext(app,g),]},	
    	1122:{'stack':[AppContext(app,g),]},	
    }
    _app_ctx_stack = LocalStack()
    

    flask上下文管理

    • 请求上下文管理 RequestContext
    • 应用上下文管理(app上下文管理) AppContext

    flask源码实现流程梗概:

    SQLHelper ,通过上下文管理、threading.local 实现

    # -*- coding: utf-8 -*-
    
    from DBUtils.PooledDB import PooledDB
    import pymysql
    import threading
    
    '''
    storage={
        111:{'stack':[]}
        111:{'stack':[]},
    }
    
    '''
    class SqlHelper(object):
        def __init__(self):
            self.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')
    
            self.local = threading.local()
    
        def open(self):
            conn = self.pool.connection()
            cursor = conn.cursor()
            return conn, cursor
    
        def close(self, cursor, conn):
            cursor.close()
            conn.close()
    
        def fetchall(self, sql, *args):
            '''获取所有数据'''
            conn, cursor = self.open()
            cursor.execute(sql, args)
            result = cursor.fetchall()
            self.close(cursor, conn)
            return result
    
        def fetchone(self, sql, *args):
            '''获取单个数据'''
            conn, cursor = self.open()
            cursor.execute(sql, args)
            result = cursor.fetchone()
            self.close(cursor, conn)
            return result
    
        def __enter__(self):
            conn, cursor = self.open()
            rv = getattr(self.local, 'stack', None)
            if not rv:
                self.local.stack = [(conn, cursor), ]
            else:
                rv.append((conn, cursor))
                self.local.stack = rv
    
            return cursor
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            rv = getattr(self.local, 'stack', None)
            if not rv:
                return
            conn, cursor = self.local.stack.pop()
            cursor.close()
            conn.close()
            
    db = SqlHelper()
    with db as cursor: #with db 执行__ernter__, cursor接受返回值
        cursor.excute('select * from xx')
    
    

    flask源码分析:

    项目启动:

    • 实例化Flask对象
    app=Flask(__name__)
    
    1.对app对象封装一些初始化的值
        static_url_path=None,
        static_folder="static",
        template_folder="templates",
    2.添加静态文件的路由static
        self.add_url_rule(
        self.static_url_path + "/<path:filename>",
        endpoint="static",
        host=static_host,
        view_func=self.send_static_file,
        )
    3.实例化了url_map对象,以后在map对象中放【/index、函数的对应关系】
    class Flask(object):
        url_rule_class = Rule
    	url_map_class = Map
    	
    	def __init__(self,...):
    		static_url_path=None,
            static_folder="static",
            template_folder="templates",
            self.view_functions = {}
            self.url_map = self.url_map_class() #Rule()
    app=Flask()
    app.view_functions
    app.url_rule_class
    
    • 加载配置文件
    from flask import Flask
    app=Flask(__name__,static_url_path='/xx')
    app.config.from_object('xx.xx')
    
    #app.config=Config
    #Config对象.from_object('xx.xx')
    #app.config
    
    1.读取配置文件中的所有键值对,并将键值对全都放到Config对象,Config继承dict,是一个字典
    	self.config = self.make_config(instance_relative_config) 
    2.把包含所有配置文件的Config对象,赋值给app.config
    
    • 添加路由映射

      1.将url=/index,和methods=[GET,POST],和endpoint='index',封装到Rule对象中
      2.将Rule对象添加到self.url_map中
      3.把endpoint和函数的对应关系放到app.view_functions
      
    • 截止目前 app对象中

      app.config
      app.url_map
      app.view_functions
      
    • 运行flask

      1.内部调用werkzeug的run_simple,内部创建socket,监听IP和端口,等待用户请求的到来
      2.一但有请求到来,执行  app对象的__call__方法
      class Flask(object):
      	def __call__(self,envion,start_response):
      		pass
      	def run(self):
      		run_simple(host, port, self, **options)
      if __name__ == '__main__':
      	app.run()
      

    用户请求到来:

    • 创建ctx=RequestContext()对象,其内部封装了Request对象和session数据

    • 创建app.ctx=AppContext()对象,内部封装了App和g

    • self.request_context(environ)
      
    • 然后ctx.push触发将ctx和app_ctx分别通过自己的LocalStack对象放入Local中,Local的本质是以线程id为key,已{‘stack’:[] } 为value的字典

      • ctx.push()
        

      注意以后取request、session、app、g的时候都要去local中获取

      ctx.request
      ctx.session
      
    • 执行所有的before_request函数 (before_first_request只在第一次执行)

      • self.try_trigger_before_first_request_functions()
        rv = self.preprocess_request()
        
    • 执行视图函数

      • rv = self.dispatch_request()
        
    • 执行所有的after_request (并将session加密放到cookie中,游览器存储cookie,下一次来带着cookie)

      self.finalize_request(rv)
      
    • 销毁ctx的app.ctx (列表为1的时候pop)

      • ctx.auto_pop(error)
        

    了解源码流程后,上下文封装的值的使用:

    from flask import request,session,current_app,g #都是LocalProxy对象
    session['x']=123 ==>本质是调用LocalProxy对象中的setitem方法 ctx.session['x']=123
    request.method ==>#调用LocalProxy对象中的getattr方法 ctx.request.method  
    current_app.config ==>app_ctx.config
    g.x1  ==>app_ctx.g.x1
    
    # context locals
    _request_ctx_stack = LocalStack()
    _app_ctx_stack = LocalStack()
    current_app = LocalProxy(_find_app)
    
    request = LocalProxy(partial(_lookup_req_object, "request"))
    session = LocalProxy(partial(_lookup_req_object, "session")) #这里的session是LocalProxy对象
    g = LocalProxy(partial(_lookup_app_object, "g"))
    
    ####
    def _lookup_req_object(name):
        top = _request_ctx_stack.top  #ctx
        if top is None:
            raise RuntimeError(_request_ctx_err_msg)
        return getattr(top, name) #返回ctx.request
    

    偏函数 functools.partial

    #偏函数示例
    def func(f1,f2):
        print(f1)
        print(f2)
    func1 = functools.partial(func,'x1',) #之后使用函数这个第一个参数就不用传了
    func1('x2') #直接传第二个参数即可
    

    LocalProxy 一个代理类

    request = LocalProxy(partial(_lookup_req_object, "request")) #相当于LocalProxy(一个函数),
    在你下次调用这个对象的时候,对这个函数进行处理(赋值或者取值session['xx']='xxx',执行对象的__setitem__,__getitem__)
    
    class LocalProxy(object):
        __slots__ = ("__local", "__dict__", "__name__", "__wrapped__")
        
        def __init__(self, local, name=None):
            object.__setattr__(self, "_LocalProxy__local", local) #
            #self._LocalProxy__local=local # 强行取私有对象 _类+变量 
            #相当于self.__local=local #私有 双下划綫+变量
            
        def _get_current_object(self):
            if not hasattr(self.__local, "__release_local__"):
                return self.__local() #相当于self._LocalProxy__local() #执行函数local,local即为传过来的函数名加括号执行。返回 ctx.session
                
        def __setitem__(self, key, value):
            self._get_current_object()[key] = value #赋值 ctx.session[key]=value
    

    g的应用

    在一次请求周期中,可以在g中设置一些值,在本次请求周期中都可以读取。

    相当于是一次请求周期中的全局变量。 (在django中实在request对象中设置值,flask中单独有g来做这一件事)

    from flask import Flask,g
    @app.before_request
    def f1():
        print('f1')
        g.x1=123
    @app.route('/')
    def hello_world():
    	print(g.x1)
        return 'Hello World!'
    

    源码流程总结:

    • 第一阶段:启动flask程序,加载特殊装饰器、路由、都封装到app=Flask()对象

    • 第二阶段:请求到来

      • 创建上下文对象:应用上下文 请求上下文
      • 执行before、视图、after
      • 销毁上下文对象

    问题:

    在flask中Local对象中为什么要通过线程id进行区分?

    因为在flask中可以开启多进程的模式(多用户),当开启多线程模式进行处理用户请求时,需要将线程之间的数据进行隔离,
    以防止数据混乱。
    

    在flask的Local对象中为什么要维持成一个栈?

    {
    	111:{'stack':[ctx,]},
    	112:{'stack':[ctx,]}, #另一个用户
    }
    {
    	111:{'stack':[app_ctx,]},
    	112:{'stack':[app_ctx,]},
    }
    在web运行时,栈永远只有一个对象,一个用户的请求时一个一个来的。
    
    在写离线脚本的时候,才会用到栈中放入多个对象,多个上下文嵌套(创建多个app)。
    #蓝图中__init__.py
    def create_app():
        app = Flask(__name__)
        app.secret_key = 'sdasfgasf'
    
        @app.route('/index')
        def index():
            return 'index'
    
        #注册 和蓝图创建关系
        app.register_blueprint(xmy,url_prefix='/web')  #url_prefix 前缀 访问/web/f1
        app.register_blueprint(xwy,url_prefix='/admin')
    
        return app
    #manage.py 
    from blue_print_flask import create_app
    from flask import current_app,g
    app1 = create_app()
    with app1.app_context():
    	print(current_app.config) #app1.config #Appcontent对象(app,g)-->local对象
    	app2=create_app()
    	with app2.app_context():
    		print(current_app.config) #app2.config
    注意:在flask中很少出现嵌套的脚本。
    

    信号:

    信号,是在flask框架中预留的钩子,让我们自定义一些操作。

    pip install blinker
    

    根据flask项目的请求流程来进行设置扩展点

    • 中间件

      from flask import Flask, flash, redirect, render_template, request
       
      app = Flask(__name__)
      app.secret_key = 'some_secret'
       
      @app.route('/')
      def index1():
          return render_template('index.html')
       
      @app.route('/set')
      def index2():
          v = request.args.get('p')
          flash(v)
          return 'ok'
       
      class MiddleWare:
          def __init__(self,wsgi_app):
              self.wsgi_app = wsgi_app
       
          def __call__(self, *args, **kwargs):
       		#这里可以加扩展点	
              return self.wsgi_app(*args, **kwargs)
       
      if __name__ == "__main__":
          app.wsgi_app = MiddleWare(app.wsgi_app)
          app.run(port=9999)
      
    • 当app_ctx被push到local栈中,会触发appcontent_pushed信号,之前注册到这个信号中的方法,就会被执行。

      class AppContext(object):
      	def push(self):
              self._refcnt += 1
              if hasattr(sys, "exc_clear"):
                  sys.exc_clear()
              _app_ctx_stack.push(self)
              appcontext_pushed.send(self.app)  #这里信号可扩展
      
      from flask import signals
      @signals.appcontext_pushed.connect
      def f1(arg):
      	print('信号f1被触发',arg)
      
    • before_first_request

      @app.before_first_request
      def f():
      	pass
      
    • request_started信号

      request_started.send(self)
      
      @signals.request_started.connect
      def f3(arg):
      	print(arg) #app
      
    • url_value_processor

      @app.url_value_preprocessor #在before_request之前执行,没有返回值。
      def f5(endpoint,args):
      	print('f5')
      @app.before_request
      def f6():
      	pass
      
    • before_request

      @app.before_request
      def f6():
      	pass
      
    • 视图函数

    • render_template

      #源码
      def _render(template, context, app):
          """Renders the template and fires the signal"""
      
          before_render_template.send(app, template=template, context=context)
          rv = template.render(context)
          template_rendered.send(app, template=template, context=context)
          return rv
      #扩展点
      from flask import Flask,render_template,signals
      @signals.before_render_template
      def f1(app, template, context):
          print('f1')
      @signals.template_rendered
      def f2(app, template, context):
          print('f2')
      
    • after_request

      @app.after_request
      
    • request_finished

      @signals.request_finished.connect
      def f6(app,response):
      	pass
      
    • got_request_exception

      @signals.got_request_exception
      def f11(app, exception):
      	pass
      
    • teardown_request

      @在finally的auto_pop中,总会执行的
      def auto_pop(self, exc):
      	...
      	self.pop(exc)
      	...
      		self.app.do_teardown_request(exc)
      @app.teardown_request  
       def f10(exc):
       	pass
      
    • 信号 request_tearing_down

       request_tearing_down.send(self, exc=exc)
      @signals.request_tearing_down
       def f11(app,exc):
       	pass
      
    • appcontext_popped

      @signals.appcontext_popped.connect
       def f11(app):
       	pass
      

    总结:一共10个信号signals

    template_rendered = _signals.signal("template-rendered")
    before_render_template = _signals.signal("before-render-template")
    request_started = _signals.signal("request-started")
    request_finished = _signals.signal("request-finished")
    request_tearing_down = _signals.signal("request-tearing-down")
    got_request_exception = _signals.signal("got-request-exception")
    appcontext_tearing_down = _signals.signal("appcontext-tearing-down")
    appcontext_pushed = _signals.signal("appcontext-pushed")
    appcontext_popped = _signals.signal("appcontext-popped")
    
    message_flashed = _signals.signal("message-flashed")
    

    message(flash):

    @signals.message_flashed

    message是一个基于Session实现的用于保存数据的集合,其特点是:使用一次就删除。

    rom flask import Flask, flash, redirect, render_template, request, get_flashed_messages
            app = Flask(__name__)
            app.secret_key = 'some_secret'
            @app.route('/')
            def index1():
                messages = get_flashed_messages()
                print(messages)
                return "Index1"
            @app.route('/set')
            def index2():
                v = request.args.get('p')
                flash(v)
                return 'ok'
            if __name__ == "__main__":
                app.run()
    

    flask-script

    flask的组件,用于运行flask程序。

    pip install flask-script
    

    其他:

    安装 flask-migrate /flask-sqlalchemy
    就支持pythoh manage.py migrate
    

    蓝图:

    • 分功能蓝图
    • 分结构蓝图 (我们称之为大蓝图)

    Flask插件:

  • 相关阅读:
    07.消除过期对象的引用
    1.1进程和多线程概述
    1.2什么是操作系统
    06.避免创建不必要的对象
    05.依赖注入优先于硬连接资源
    04.使用私有构造器执行非实例化
    03.使用私有构造方法或枚类实现 Singleton 属性
    02.当构造参数过多时使用builder模式
    01.考虑使用静态工厂方法替代构造方法
    iiS申请地址
  • 原文地址:https://www.cnblogs.com/hanfe1/p/14223888.html
Copyright © 2020-2023  润新知