Flask 和 Django的区别
Django
Django大而全,功能齐全,开发效率高,但缺点是过于臃肿
Flask
Flask小而精,轻量灵活,但缺点是很多东西需要自己实现或者引入第三方插件
几行代码即可运行起来,极致简洁
# 三行代码启动一个flask服务器
from flask import Flask
app = Flask(__name__)
app.run()
# 六行代码实现一个flask接口
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello Flask!"
app.run(host="127.0.0.1")
Flask 依赖
必须要依赖的
Jinja2:一个用以模板引擎渲染的库
Werkzeug:Web框架的底层库,【本质上是一个socket服务端】,某种程度而言,Flask是Werkzeug的扩展
常用的依赖
Flask-SQLAlchemy:比较常见的MySql ORM库
Flask-Migrate:ORM迁移库
celery:定时任务/异步任务调度库
flask-mongoengine:MongoDB ORM库
redis:redis数据库操作库
使用Werkzeug手写一个简单的框架
from werkzeug.serving import run_simple
class MyFlask(object):
def __call__(self, environ, start_response):
print('----------请求进来----------')
# flask万物起源,其内所有操作都在这一行代码内完成
# return self.wsgi_app(environ, start_response)
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'Hello, World!']
def run(self):
# run_simple内部处理请求时会调用self(),因此会触发__call__方法
# self()
run_simple('127.0.0.1', 8000, self)
if __name__ == '__main__':
app = MyFlask()
app.run()
Flask特殊装饰器
请求前后钩子,类似django的中间件
@app.before_request :请求前钩子
@app.after_request :请求后钩子,被装饰函数要接受一个参数response,并且需要在被装饰函数内return该参数
@app.before_first_request :第一次请求前钩子,只有在第一次请求进来前执行
路由管理
@app.route :将url和函数打包成rule,封装到map对象,map再放到app中
@app.do_teardown_request :在请求结束时执行,无论请求成功与否
信号
@signals.appcontext_pushed.connect :在将应用上下文push到栈时触发,被装饰函数要接受一个参数app
@signals.appcontext_popped.connect :在将应用上下文pop出栈时触发,被装饰函数要接受一个参数app
@signals.request_started.connect :在请求准备开始处理之前触发,被装饰函数要接受一个参数app
@signals.before_render_template.connect :在渲染模板前触发,接收参数:app、template、context(模板参数)
@signals.template_rendered.connect :在渲染模板后执行,接收参数:app、template、context(模板参数)
@signals.request_finished.connect :在请求视图函数完成后执行,接收参数:app、response
@signals.got_request_exception.connect :在请求出错时捕获到异常时执行,接收参数:app、exception
@signals.request_tearing_down.connect :在请求结束时pop请求上下文时执行,接收参数:app、exc
@signals.appcontext_tearing_down.connect :在请求结束时pop应用上下文时执行,接收参数:app、exc
threading.local 的作用
用于为每个线程开辟一块空间来存取该线程独有的值
Flask中并没有用到这个,但Flask自身重新实现了一个类似的【Local】
import time
import threading
class Test:
def __init__(self,num):
self.num = num
# 多个线程都修改同一个值,所以打印出来的结果均相同
# test_obj = Test(0)
# 多个线程各自维护自己的值,所以打印出来的值各不相同
test_obj = threading.local()
def task(i):
test_obj.num = i
time.sleep(1)
print(test_obj.num)
if __name__ == '__main__':
for i in range(5):
t = threading.Thread(target=task,args=(i,))
t.start()
Flask 自己实现的Local【上下文管理】
Flask 实现的Local类和threading.local 功能相似,为每个线程/协程开辟空间存取数据,实现机制都是内部维护一个字典,以线程/协程ID为key,进行数据的隔离,如:storage = {12312:{'stack':1},12432:{'stack':2}}
Flask 还实现了一个LocalStack类,它内部依赖于Local,Local用以具体存取数据,而它则用于将Local维护成一个栈,Flask外部使用时,均为使用此类,注意此类应该为单例模式,一个程序永远只有一个同类型的localstack对象
注:为什么要将stack维护成一个栈呢?主要目的是为了解决多个app的问题,比如离线脚本等。秉承“先进后出,后进先出”原则
一个Flask对象,一个有2个localstack,分别是:请求上下文(request_ctx_stack) 和 应用上下文(app_ctx_stack)
# context locals
__storage__ = {
1111: {'stack': [RequestContext(request, session)]},
2222: {'stack': [RequestContext(request, session)]}
}
_request_ctx_stack = LocalStack()
__storage__ = {
1111: {'stack': [AppContext(app, g)]},
2222: {'stack': [RequestContext(app, g)]}
}
_app_ctx_stack = LocalStack()
# 一个请求进来时,把构建请求上下文和应用上下文,push到对应的Local内
_request_ctx_stack.push(RequestContext(request, session))
_app_ctx_stack.push(AppContext(app, g))
原理代码实现【简写】:
import time
import threading
class Local:
def __init__(self):
# 存放数据,格式:{'线程/协程id1':{'stack':[]},'线程/协程id2':{'stack':[]}}
super(Local, self).__setattr__('storage', {})
# 获取线程唯一标识,flask中兼容了协程,可以获取协程唯一标识
super(Local, self).__setattr__('ident', threading.get_ident)
def __getattr__(self, item):
if self.ident() not in self.storage:
return
return self.storage[self.ident()][item]
def __setattr__(self, key, value):
try:
self.storage[self.ident()][key] = value
except KeyError:
self.storage[self.ident()] = {key: value}
def release_local(self):
# 释放线程/协程本地资源
del self.storage[self.ident()]
class LocalStack:
def __init__(self):
self._local = Local()
def top(self):
# 返回栈顶
stack = getattr(self._local, 'stack', None)
return stack[-1]
def pop(self):
# 出栈
stack = getattr(self._local, 'stack', None)
if len(stack) == 1:
# 当栈内数据只有1个时,释放调线程/协程的本地资源
self._local.release_local()
return stack.pop()
def push(self, obj):
# 入栈
stack = getattr(self._local, 'stack', None)
# 数据不存在时,初始化一个
if not stack:
self._local.stack = stack = []
stack.append(obj)
return stack
if __name__ == '__main__':
stack = LocalStack()
def task(i):
stack.push(i)
stack.push(i + 1)
stack.push(i + 2)
stack.pop()
print(stack._local.storage)
# {746504: {'stack': [0, 1]}}
# {746504: {'stack': [0, 1]}, 757820: {'stack': [1, 2]}}
for i in range(2):
t = threading.Thread(target=task, args=(i,))
t.start()
偏函数
在functools包中有partial函数,其作用为将传入函数封装成一个新的函数,并可自动传递值进目标函数
from functools import partial
def request_ctx(attr):
a = {'request': 1, 'session': 2}
return a.get(attr)
def app_ctx(attr):
a = {'app': 3, 'g': 4}
return a.get(attr)
if __name__ == '__main__':
request = partial(request_ctx, 'request')
session = partial(request_ctx, 'session')
g = partial(app_ctx, 'g')
print(request)
print(session)
print(g)
print(request(),session(),g())
# 此时得到的输出内容为三个函数,输出结果如下:
# functools.partial(<function request_ctx at 0x000001CD40154700>, 'request')
# functools.partial(<function request_ctx at 0x000001CD40154700>, 'session')
# functools.partial(<function app_ctx at 0x000001CD40154940>, 'g')
# 1 2 4
Flask使用LocalProxy结合偏函数取app、g、request、session
def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)
def _lookup_app_object(name):
top = _app_ctx_stack.top
if top is None:
raise RuntimeError(_app_ctx_err_msg)
return getattr(top, name)
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(partial(_lookup_app_object,"app"))
g = LocalProxy(partial(_lookup_app_object, "g"))
request = LocalProxy(partial(_lookup_req_object, "request"))
session = LocalProxy(partial(_lookup_req_object, "session"))