1. 中间件
```handlebars
Flask的中间件与Django的中间件完全不一样.
需要实现Django的中间件效果使用请求拓展.
Flask的中间件, 是启动Flask服务器程序前后做一些自定义功能.
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello World!'
# 模拟中间件
class Md(object):
def __init__(self,old_wsgi_app):
self.old_wsgi_app = old_wsgi_app
def __call__(self, environ, start_response):
print('开始之前')
ret = self.old_wsgi_app(environ, start_response)
print('结束之后')
return ret
if __name__ == '__main__':
# 那我们希望在执行他本身的wsgi之前做点事情.
# 1. 我们发现当执行app.run方法的时候, 最终执行run_simple, 最后执行app(), 也就是在执行app.__call__方法
# 2. 在__call__里面,执行的是self.wsgi_app().
# 3. 所以我们先用Md类中__init__, 保存之前的wsgi,然后我们用将app.wsgi转化成Md的对象.
# 4. 那执行新的的app.wsgi_app,就是执行Md的__call__方法, 把原来的wsgi_app替换为自定义的.
app.wsgi_app = Md(app.wsgi_app)
app.run()
2. 程序目录划分
目录结构:
项目名
|--templats html模板文件
|--static 静态文件夹
|--views 视图函数
|--__init__.py
|--user.py 视图函数1
|--order.py 视图函数1
|--app.py 启动程
* 0. 新建项目 Flaks_Directory_Partition
* 1. 在项目下新建templats目录, 并新建order.html
* 2. 在项目目录下新建static目录, 复制一张图片到static中
* 3. 在项目下新建views包, 在views包下新建user.py 与 order.py
* 4. 在项目下新建app.py
# __init_py
from flask import Flask
# 模板目录默认是同级的templates , 静态文件目录默认是同级的static
# __init__.py 与 templates不在同一级目录, 使用../templates 去上一级目录中找, static_folder同理
app = Flask(__name__, template_folder='../templates', static_folder='../static')
# 导入视图(必须导入, 相当于将users.py的代码与order.py复制到这个位置, 最后由app.py启动)
from . import users
from . import order
# users.py
from . import app
@app.route('/users')
def users():
# 返回字符串信息
return 'users'
# order.py
from . import app
from flask import render_template
@app.route('/order')
def user():
# 返回html页面
return render_template('order.html')
<!-- templates/order.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- 展示图片 -->
<img src="/static/dijia.png" alt="">
</body>
</html>
# app.py
from views import app
if __name__ == '__main__':
app.run()
测试:
访问 http://127.0.0.1:5000/users 显示字符串
访问 http://127.0.0.1:5000/order 显示html页面, 并加载图片.
常见问题: 多个文件中使用 from . import app 很容易出现循环导入问题, 所以并不推荐这样使用.
3. 蓝图(Blueprint)
蓝图的作用: 对flask程序进行目录结构的划分时, 避免循环导入问题.
蓝图的使用:
1. 在视图中使用蓝图
from flask import Blueprint
蓝图对象 = Blueprint('路由名称', __name__) # 路由名称不带/
@蓝图对象.route('路由')
...
2. __init__.py 中注册
app.register_blueprint(蓝图对象)
生成蓝图对象, 使用蓝图对象装饰器作用在视图函数之后才有作用, 否则访问的话, 路由不识别
想要在__init__中生成蓝图对象, 接着注册, 然后将蓝图导入视图函数, 再装饰视图函数行不通.
3. 请求拓展使用(请求拓展只在当前文件生效)
@蓝图对象.请求拓展
* 再不同的文件中注册路由时, 直接使用蓝图对象注册, 不在使用 from . import app , 解决循环导入问题.
路由前缀的使用:
蓝图对象 = Blueprint('路由名称', __name__, url_prefix='路由前缀') # 路由名称不带/
@蓝图对象.route('路由')
@蓝图对象.route('/路由') # 之后访问路由就需要添加前缀
* 0. 修改users.py
# users.py
from flask import Blueprint
users_blue = Blueprint('users', __name__)
# 请求拓展
@users_blue.before_request
def process_request(*args, **kwargs):
print('请求来了!')
# 视图函数
@users_blue.route('/users')
def users():
return 'users'
* 1. 修改order.py
# order.py
from flask import Blueprint, render_template, url_for
order_blue = Blueprint('order',
__name__,
template_folder='../templates', # 设置模板文件
static_folder='../static',
static_url_path='/xx', # 访问静态文件前面需要添加的地址
url_prefix='/aa' # 访问路由需前面需要添加的地址
)
# 默认 static_url_path='/static' 访问地址: http://127.0.0.1:5000/url_prefix/order/static/dijia.png
# static_url_path='' 访问地址: http://127.0.0.1:5000/url_prefix/order/dijia.png
# url_prefix 没有设置,访问地址: http://127.0.0.1:5000/order/dijia.png
# 请求拓展
@order_blue.before_request
def process_request(*args, **kwargs):
print('请求来了!')
@order_blue.route('/order')
def users():
return render_template('order.html')
<!-- template/order.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--static_url_path='/xx' 与 url_prefix='/aa' 那么 src = "/aa/xx/dijia.png" -->
<img src="/dijia.png" alt="">
</body>
</html>
* 2. 修改__init__.py
from flask import Flask
# 导入视图
from . import users
from . import order
# 生成app对象
app = Flask(__name__)
# 注册蓝图
app.register_blueprint(users.users_blue)
app.register_blueprint(order.order_blue)
注意:
Flask类初始化设置template_folder与static_folder
app = Flask(__name__, template_folder='../templates', static_folder='../static')
模板文件使用图片: src = "/static/dijia.png"
Blueprint类初始化设置template_folder与static_folder , 在使用静态文件,地址有一些变化.
Blueprint('路由名称', __name__, template_folder='../templates', static_folder='../static',
static_url_path='') # 多了一个static_url_path参数.
模板文件使用图片: src = "/静态文件路由地址/dijia.png",
静态文件路由地址为空可以省略, src = "/静态文件路由地址/dijia.png"
这样可以为每个视图设置单独的模板文件目录与静态文件目录.
4. 中小型项目目录
项目名
|--项目名包
|--templates 模板文件茉莉
|--static 静态文件目录
|--views 视图
|--x.py 视图函数
...
|--__init__.py 初始化程序
|--run.py 启动程序
* 0. 新建Python项目Flask_Small_Projects
* 1. 在项目下新建Flask_Small_Projects包 与 run.py
* 2. 在Flask_Small_Projects包下新建templates目录, static目录, views目录
* 3. 在views目录中新建users.py
* 4. 在templates目录目录中新建users.html
* 5. 在static目录目录中添加图片↓
# __init__.py
from flask import Flask
from .views import users
# 模板目录默认是同级的templates , 静态文件目录默认是同级的static
app = Flask(__name__)
# 注册蓝图对象
app.register_blueprint(users.users_blue)
# users.py
from flask import Blueprint, render_template
# 生成蓝图对象, 不单独设置模板与静态文件 使用app对象的.
users_blue = Blueprint('users', __name__) # 这里会有一个默认参数static_url_path='/static'
# 可以设置 static_url_path='', 那么再模板中使用就不需要加上/static
# 请求拓展
@users_blue.before_request
def process_request():
print('请求来了!')
# 视图函数
@users_blue.route('/users')
def users():
return render_template('users.html')
<!--templates/users.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>123</h1>
<!-- static_url_path='/static' 使用的时候使用的时候一定要加/static -->
<img src="/static/dijia.png" alt="">
</body>
</html>
# __init__.py
from Flask_Small_Projects import app
if __name__ == '__main__':
app.run()
5. 大型项目目录
项目名
|--项目名包
|--app1 app1
|--templates app1的模板文件目录
|--static app1的静态文件目录
|--views.py 视图
|--__init__.py app1的初始化文件
|--app2 app2
|--templates app2的模板文件目录
|--static app2的静态文件目录
|--views.py 视图
|--__init__.py app2的初始化文件
|--__init__.py 项目初始化文件
|--run.py 启动文件
* 0. 新建Python项目Flask_Big_Projects
* 1. 在项目下新建Flask_Big_Projects包 与 run.py
* 2. 在项目下新建app, admin
* 3. 在admin app下新建templates包, static目录, views文件
* 4. 在views文件写逻辑
* 5. 在templates目录目录中新建admin.html
* 6. 在static目录目录中添加图片↓
...
# admin/__init__.py
from flask import Blueprint
# 配置自己的模板目录与静态文件目录, 找到同级别的目录
admin_blue = Blueprint('admin', __name__,
template_folder='templates',
static_folder='static',
static_url_path='',
)
# PEP8规范提示不要理会, 顺序不要乱, 否则会报错
from . import views
# views.py
from . import admin_blue
from flask import render_template
# 拓展请求
@admin_blue.before_request
def process_request():
print('请求来了')
# 视图函数
@admin_blue.route('/admin')
def admin():
return render_template('admin.html')
<!-- admin/templates/admin.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<img src="/dijia.png" alt="">
</body>
</html>
# Flask_Big_Projects/__init__.py
# 创建app
from flask import Flask
app = Flask(__name__)
# 导入蓝图
from . import admin
# 注册蓝图
app.register_blueprint(admin.admin_blue)
# run.py
from Flask_Big_Projects import app
if __name__ == '__main__':
app.run()
6. Local类解析
6.1 request区分访问用户
Flask中使用全局request, 谁访问, requesr就是谁, 那么request是如何做到可以区分访问用户的?
同一个时刻有三个用户访问, 那么flask会开启三个线程, 去处理请求.
会依据当前的线程号区分访问用户.
使用多线程存在的问题:
* 1. GIL锁
* 2. 多线程下修改数据不安全
GIL锁问题 (Python多线程执行的效率取决于谁开启线程.)
开启一个进程会调用Cpython解释器执行,
如果使用wigiref(Python写的), Cpython解释器下启动线程执行Flask代码,
同一个进程下, 由于GIL锁的限制, 无法实现多cpu同时执行, 同一时间只有一个cpu执行一个线程, 限制了效率.
使用uwsgi(C语言写的)C语言启动线程执行Flask代码, 避免了GIL锁的限制.
如果在项目中在开多线程效率还是会降低.
Flask重写了threading.local方法, 可以保证多线程下修改数据的安全. (不加锁实现, 加锁程序由并发变成串行执行.)
threading.local对象会根据当前的线程号, 创建一个全局大字典, key是线程的id, value是又是一个字典存用户数据.
a = {'线程id': {a:1}, '线程id':{a:2}}
多个用户使用同一个变量时保证了不同线程操作时不同的数据, 不会出现错乱的情况.
6.2 多线程修改数据
# 多线程修改同一个值
from threading import Thread
import time
# 定义一个全局变量
num = 0
# 线程执行的函数
def modify(i):
# 声明num函数外部的变量, 这里就是全局变量了
global num
# 修改num的值
num = i
# 阻塞(GIL限制同一时刻只有一个线程在执行, 线程阻塞之后切换线程执行, 其他9个线程也会在这里阻塞)
time.sleep(1)
# 打印num的值, 9个线程改同一个值, 谁最后改值就是谁
print(f'{num}\n', end='')
for i in range(10):
t = Thread(target=modify, args=(i,))
t.start()
终端显示:
9
9
9
9
9
9
9
9
9
9
________________________
多线程下修改数据是不安全的
6.3 原生local的使用
# 使用local对象
from threading import Thread, local
import time
# 生成一个local对象
num = local()
"""
local对象可以在任何位置调用,
赋值: local对象.value = xx
取值: local对象.value
"""
# 线程执行的函数
def modify(i):
num.value = i
# 阻塞(阻塞之后切换线程, 其他9个线程也会在这里阻塞)
time.sleep(1)
# 打印num的值, 9个线程改同一个值, 谁最后改值就是谁
print(f'{num.value}\n', end='')
for i in range(10):
t = Thread(target=modify, args=(i,))
t.start()
终端显示:
9
0
7
6
1
8
2
4
5
3
__________________________________
local方法实现了多线程下修改数据的安全
6.4 模仿local
1. 函数版本
# get_ident获取线程id
from threading import Thread, get_ident
# 定义一个全局变量存所有线程的值
thread_dict = {}
# 保存修改值
def set_value(i):
# 获取线程id做为第一层字典的键
ident = get_ident()
# 存值
if i not in thread_dict:
# 第二层键为value
thread_dict[ident] = {'value': i}
# 改值
else:
thread_dict[ident]['value'] = i
# 获取值
def get_value(i):
# 获取线程id做
ident = get_ident()
v = thread_dict[ident]['value']
# 将值返回
return v
# 线程调用的方法
def task(i):
# 存
set_value(i)
# 查
v = get_value(i)
print(f'{v}\n', end='')
t_list = []
# 开启10个线程
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
t_list.append(t)
# 等子线程执行完毕
for t in t_list:
t.join()
# 查看构造的字典
print(thread_dict)
终端显示:
0
1
2
3
4
5
6
7
8
9
{4768: {'value': 0}, 11204: {'value': 1}, 696: {'value': 2}, 7092: {'value': 3},
10456: {'value': 4}, 6608: {'value': 5}, 4668: {'value': 6}, 9632: {'value': 7},
6328: {'value': 8}, 3028: {'value': 9}}
2. 封装成对象1
# get_ident获取线程id
from threading import Thread, get_ident
# 自定义local对象
class local(object):
# 类属性
thread_dict = {}
# 保存修改值
def set(self, i):
# 获取线程id做为第一层字典的键
ident = get_ident()
# 存值
if i not in local.thread_dict:
# 第二层键为value
local.thread_dict[ident] = {'value': i}
# 改值
else:
local.thread_dict[ident]['value'] = i
# 获取值
def get(self, i):
# 获取线程id做
ident = get_ident()
v = local.thread_dict[ident]['value']
# 将值返回
return v
# 创建local对象
local_obj = local()
# 线程调用的方法
def task(i):
# 修改and保存
local_obj.set(i)
# 查询
v = local_obj.get(i)
print(f'{v}\n', end='')
t_list = []
# 开启10个线程
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
t_list.append(t)
# 等子线程执行完毕
for t in t_list:
t.join()
print(local.thread_dict)
终端显示:
0
1
2
3
4
5
6
7
8
9
{15864: {'value': 0}, 5532: {'value': 1}, 10376: {'value': 2},
12148: {'value': 3}, 1328: {'value': 4}, 13172: {'value': 5},
16356: {'value': 6}, 3228: {'value': 7}, 1900: {'value': 8}, 8888: {'value': 9}}
3. 封装成对象2
使用__setattr__与__getattr__方法
# get_ident获取线程id
from threading import Thread, get_ident
# 自定义local对象
class local(object):
# 类属性
thread_dict = {}
# 保存修改值
def __setattr__(self, key, value):
# 获取线程id做为第一层字典的键
ident = get_ident()
# 存值
if i not in local.thread_dict:
# 第二层键为value
local.thread_dict[ident] = {key: value}
# 改值
else:
local.thread_dict[ident][key] = value
# 获取值
def __getattr__(self, item):
# 获取线程id做
ident = get_ident()
item = local.thread_dict[ident][item]
# 将值返回
return item
# 创建local对象
local_obj = local()
# 线程调用的方法
def task(i):
# 修改and保存
local_obj.value = i
# 查询
item = local_obj.value
print(f'{item}\n', end='')
t_list = []
# 开启10个线程
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
t_list.append(t)
# 等子线程执行完毕
for t in t_list:
t.join()
print(local.thread_dict)
0
1
2
3
4
5
6
7
8
9
{7508: {'value': 0}, 7272: {'value': 1}, 13748: {'value': 2}, 3092: {'value': 3}, 13940: {'value': 4}, 8384: {'value': 5}, 15784: {'value': 6}, 14772: {'value': 7}, 13456: {'value': 8}, 14092: {'value': 9}}
4. 封装对象3
每次实例化得到一个对象使用自己的字典, 而不是多个对象使用同一个字典.
多个对象使用一个字典会出问题. (某个线程被重复使用就会导致存值更新而不是添加.)
# get_ident获取线程id
from threading import Thread, get_ident
# 自定义local对象
class local(object):
def __init__(self):
# 只要自己定义了__setattr__ 想要使用 对象.属性 = 值 就需要注意无限循环问题
# self.thread_dict = {} 不能使用,
# 在父类中更新子类的值
object.__setattr__(self, 'thread_dict', {})
# 保存修改值
def __setattr__(self, key, value):
# 获取线程id做为第一层字典的键
ident = get_ident()
# 存值
if i not in self.thread_dict:
# 第二层键为value
self.thread_dict[ident] = {key: value}
# 改值
else:
self.thread_dict[ident][key] = value
# 获取值
def __getattr__(self, item):
# 获取线程id做
ident = get_ident()
item = self.thread_dict[ident][item]
# 将值返回
return item
# 创建local对象
local_obj = local()
# 线程调用的方法
def task(i):
# 修改and保存
local_obj.value = i
# 查询
item = local_obj.value
print(f'{item}\n', end='')
t_list = []
# 开启10个线程
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
t_list.append(t)
# 等子线程执行完毕
for t in t_list:
t.join()
print(local_obj.thread_dict)
# 每一个新创建的对象有自己的字典
obj = local()
print(obj.thread_dict)
终端显示:
0
1
2
3
4
5
6
7
8
9
{13244: {'value': 0}, 12544: {'value': 1}, 7208: {'value': 2}, 8944: {'value': 3},
13464: {'value': 4}, 3016: {'value': 5}, 4784: {'value': 6}, 10492: {'value': 7},
8224: {'value': 8}, 10216: {'value': 9}}
{}
5. 携程中使用
# 获取携程id
from greenlet import getcurrent as get_ident
from gevent import monkey
from gevent import spawn
# 检测所有的IO行为
monkey.patch_all()
# 自定义local对象
class local(object):
def __init__(self):
object.__setattr__(self, 'thread_dict', {})
# 保存修改值
def __setattr__(self, key, value):
ident = get_ident()
# 存值
if i not in self.thread_dict:
self.thread_dict[ident] = {key: value}
# 改值
else:
self.thread_dict[ident][key] = value
# 获取值
def __getattr__(self, item):
# 获取线程id做
ident = get_ident()
item = self.thread_dict[ident][item]
# 将值返回
return item
# 创建local对象
local_obj = local()
# 携程调用的方法
def task(i):
# 修改and保存
local_obj.value = i
# 查询
item = local_obj.value
print(f'{item}\n', end='')
g_list = []
# 开启10个线程
for i in range(10):
g = spawn(task, i)
g.start()
g_list.append(g)
# 等子线程执行完毕
for g in g_list:
g.join()
print(local_obj.thread_dict)
终端显示:
0
1
2
3
4
5
6
7
8
9
{<Greenlet at 0x20ef702b040: _run>: {'value': 0},
<Greenlet at 0x20ef702b260: _run>: {'value': 1},
<Greenlet at 0x20ef702b150: _run>: {'value': 2},
<Greenlet at 0x20ef702b370: _run>: {'value': 3},
<Greenlet at 0x20ef702b480: _run>: {'value': 4},
<Greenlet at 0x20ef702b590: _run>: {'value': 5},
<Greenlet at 0x20ef702b7b0: _run>: {'value': 6},
<Greenlet at 0x20ef702b6a0: _run>: {'value': 7},
<Greenlet at 0x20ef702b8c0: _run>: {'value': 8},
<Greenlet at 0x20ef702b9d0: _run>: {'value': 9}}
文章的段落全是代码块包裹的, 留言0是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言1是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言2是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言3是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言4是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言5是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言6是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言7是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言8是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言9是为了避免文章提示质量低.