2. Flask中间件&&蓝图

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

image-20221010210521174

# __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()

2022-10-08_00834

测试:
访问 http://127.0.0.1:5000/users 显示字符串
访问 http://127.0.0.1:5000/order 显示html页面, 并加载图片.

image-20221010182004647

常见问题: 多个文件中使用 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"

这样可以为每个视图设置单独的模板文件目录与静态文件目录.

2022-10-11_00839

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目录目录中添加图片↓

image-20221010210521174

# __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()

image-20221011002619645

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目录目录中添加图片↓
...

image-20221010210521174

# 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()

2022-10-11

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是为了避免文章提示质量低.


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值