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插件:
- WTForms
- SQLAchemy
- 等... http://flask.pocoo.org/extensions/