良好的框架应该有良好的扩展性
试想我有如下需求:
- 在服务器启动时,我需要往数据库初始化一些数据
- 在每次请求api之前,需要验证用户token是否合法
- 每次请求完成后,需要往数据库插入一条日志
你可能会这么做,我先去找到服务器启动时的代码位置,在他之前插入一段代码;
在请求分发到路由之前,我找到源码的位置,然后再此处插入一段代码。。。。。。
如果一个框架不在某些位置留几个口子留给你扩展的话,必须继承原来的类进行扩展,甚至重写覆盖方法都无法完成需求时,那么这不是一个成熟的框架;良好的框架设计时就已经预埋了扩展点,比如我们mes后端代码基类里的,before_save_one,before_delete_one等等,类似这种概念
我总结了在flask框架中留给用户扩展的两种方式:
- 钩子函数:把你需要执行的函数全部注册进来,框架会在运行到此处时帮你一一执行
- 信号机制:
钩子函数
钩子函数是指在执行函数和目标函数之间挂载的函数,框架开发者给调用方提供一个point-挂载点,至于挂载什么函数由调用方决定
from flask import Flask, request,session
from flask import signals
app = Flask(__name__)
@app.before_first_request
def fun1():
print('启动服务器时执行,里面做了一个标识,后面就不会再执行了')
@app.before_request
def fun2():
print('每次请求之前都会执行')
@app.after_request
def fun3(response):
print('每次请求之后执行,传入一个response对象')
return response
@app.teardown_request
def fun4(exc):
print('含泪执行,无论如何都会执行')
@app.errorhandler(404)
def errorhandler(e):
# 当访问一个不存在的url时
return '出错啦!'
@app.route('/')
def index():
return '12'
if __name__ == '__main__':
app.run(debug=True)
信号机制
- 概念:信号就是在框架核心功能或者一些Flask扩展发生工作时所发送的通知,用于帮助你解耦应用
- 你可以认为是当你在环游全球时,每到达一个国家,你发一个朋友圈打卡,广播告诉你的朋友们你到了,然后看到你的朋友圈的朋友开始给你回复。
- 信号看起来和钩子做类似的事情
- 信号依赖于Blinker库,但是也可以使用flask封装的namespace避免安装blinker库
blinker使用示例
from blinker import signal
# 信号发布者
s1 = signal('s1')
# 订阅者1号
def func1(sender):
print(sender)
# 订阅者2号
def func2(sender):
print(sender)
s1.connect(func1, sender=1) # sender为1时,订阅号一号才会执行
s1.connect(func2)
for i in range(3):
s1.send(i)
flask信号使用示例
from flask import Flask, request,session
from flask.signals import request_started,request_finished
app = Flask(__name__)
def fun1(app):
print(app)
print('每次请求之前都会执行')
def fun2(*args, **kwargs):
print(args,kwargs)
print('每次请求完成之后执行,传入一个response对象')
request_started.connect(fun1)
request_finished.connect(fun2)
@app.route('/')
def index():
return '12'
if __name__ == '__main__':
app.run(debug=True)
总结
- 信号看起来和钩子做同样的事情。然而在工作方式上它们存在不同。譬如核心的before_request()处理程序以特定的顺序执行,并且可以在返回响应之前放弃请求。相比之下,所有的信号处理器是无序执行的,并且不修改任何数据,并且无法中断请求。
- 一般来说,钩子用于改变行为(比如,身份验证或错误处理),而信号用于记录事件(比如记录日志)
- 此处可以到源码中找一些信号埋放的位置,展示给大家看