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, '\r\n')

   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插件:

posted @ 2021-01-02 20:51  hanfe1  阅读(260)  评论(0编辑  收藏  举报