一,Flask初识
Python现阶段三大主流web框架Django,Tornad,Flask对比
1.Django主要特点是大而全,集成了很多组件,例如:Models Admin Form等等,不管是否用上,它全都有,属于全能型框架,Django通常用于大型web应用由于内置组件足够强大所以使用Django开发可以一气呵成,缺点是浪费资源,一次性加载全部资源,肯定会造成一部分资源的浪费想·
2.Tornado主要特点是原生异步非阻塞,在IO密集型应用和多任务处理上占据绝对性的优势,属于专注型框架,通常用于api后端应用,游戏服务后台,内部实现的异步非阻塞非常牛逼,缺点是干净,不支持Session
3.Flask主要特点小而轻,原生组件几乎为0,三方提供的组件请参考django非常全面,属于短小精悍型框架,通常应用于小型应用和快速构建应用,其强大的三方库,足以支撑一个大型的web应用
Flask 安装
pip install flask
二,Flask的WSGI网关接口协议
WSGI网关接口协议 django: wsgi_ref 封装request,封装socket,一般用于django本地测试 uwsgi django上线使用,性能更好 flask: Werkzeug是Python的WSGI规范的实用函数库。使用广泛,基于BSD协议 werkzeug为Flask封装了socket from werkzeug.wrappers import Request, Response from werkzeug.serving import run_simple @Request.application def run(request): return Response("hello~~") if __name__ == '__main__': run_simple('localhost', 5000, run) # 功能特性 HTTP头解析与封装 易于使用的request和response对象 基于浏览器的交互式JavaScript调试器 与 WSGI 1.0 规范100%兼容 支持Python 2.6,Python 2.7和Python3.3 支持Unicode 支持基本的会话管理及签名Cookie 支持URI和IRI的Unicode使用工具 内置支持兼容各种浏览器和WSGI服务器的实用工具 集成URL请求路由系统
三,Flask的demo
from flask import Flask # 注意静态文件以及模板的配置 # 默认tamplates static app = Flask(__name__) @app.route("/") def index(): return # 可以返回的类型 render_templage()/redirect()/"字符串" app.run()
四,Flask的配置文件
# 配置文件 # 配置信息 app.config # 修改配置信息 app.config["DEBUG"] = True # 解耦写法 -- settings.py class DEVConfig(object): DEBUG = True SECRET_KEY = "jalksdjgajh" class ProConfig(object): DEBUG = False class TestConfig(object): TESTING = True -- app.config.from_object("settings.DEVConfig") # from_object设置配置文件类
# 可以直接用app.配置的项
app.testing
app.secret_key
app.session_cookie_name
app.permanent_session_lifetime
app.send_file_max_age_default
app.use_x_sendfile
五,Flask的路由
# 一般的路由 @app.route("/book") # 带参数的路由 @app.route("/book/<int:nid>") # 参数类型 不设置数据类型 则默认为str类型 # 参数的数据类型:略 # 路由的命名: @app.route("/book",endpoint="book") # 不配置的话,endpoint默认为视图函数名 # 命名路由的反向解析 from flask import url_for url_for("name",nid=xxx) # => /book/123 return redirect(url_for("xxx",nid='xx'))
路由的实现原理
@app.route("/index") # 带参数的装饰器 decorator = app.route("/index") # 源码 def route(self, rule, **options): def decorator(f): endpoint = options.pop('endpoint', None) self.add_url_rule(rule, endpoint, f, **options) return f return decorator @decorator def index(): pass app.add_url_rule(rule,endpoint=None,view_func=视图函数) # 可以通过这个方式来创建对象关系
路由的正则匹配
# 源码自带的正则匹配类 UnicodeConverter AnyConverter PathConverter NumberConverter IntegerConverter FloatConverter UUIDConverter
# 自定义正则 class RegexConverter(BaseConverter): # 自定义URL匹配正则表达式 def __init__(self,map,regex): super(RegexConverter,self).__init__(map) self.regex = regex def to_python(self,value): # 路由匹配是,匹配成功后传递给视图函数中参数的值 return int(value) def to_url(self,value): # 使用url_for 反向生成URL时,传递的参数经过该方法处理,返回值用于生成URL参数 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")) print(nid) print(type(nid)) return "Index"
六,Flask的请求相关以响应相关
# 请求相关 # flask的request不如django一样贯穿整个请求的声明周期,那么python怎么识别哪个请求对的哪个request呢: 以协程作为唯一标志为key:具体请求的数据为value # 使用历史于字典的数据结构 from flask import request
# 常用的请求相关的数据
request.method # 请求类型
request.headers # 请求头 request.args # url的参数?xx=xx reuqest.form # 表单的数据 request.files # 上传文件
request.path # 获取url
request.full_path # 获取完整url # 上传文件
obj = request.files['file_name'] obj.save('/var/www/uploads/'+ secure_filename(f.filename)) # 响应相关 # response三种类型 return str return render_template return redirect # 自定义响应 from flask import make_response # 封装响应对象 response = make_response(render_template("xxx.html")) response.set_cookie("key","value") # 设置cookie response.header["X-Something"] = "A value" # 设置响应头 return response
七,Flask的模板渲染
# flask的模板渲染 跟django的模板语言 基本相同 # 区别 # 函数的执行需要加() {{my_func()|safe}} # 字典的三种取值方式 {{ my_dict.ages }} {{ my_dict.["ages"] }} {{ my_dict.get("ages", 0) }} # 给页面传递数据,包括函数 return render_template("xxx.html",**{"book_list":book_list,"myfunc":myfunc})
八,Session
# session flask的session底层:base64 app.config["SECRET_KEY"]="xx" # 配置盐 session['userinfo'] = {"name":name } # 设置session session.get("userinfo") # 在session中取值
session.pop("key") # 删除
# flash 闪现:只能在一个请求里拿值 from flask import flash,get_flashed_message
# 原理 # 设置值的时候 session["xxx"]=value flash("value","key") # 取值 session.pop("xxx") name = get_flashed_messages() name = get_flashed_messages(category_filter=["name"])
九,视图
# 路由的实现原理 # @app.route("/index") decorator = app.route("/index") @decorator def index(): return "xxx" app.add_url_rule(rule,endpoint,f) # rule = "/index" # endpoint = 别名 # f = 视图函数名 # 注意在add_url_rule方法里 # endpoint默认取函数名 # 两个函数不能用一个endpoint
# CBV编程 class MyView(views.MethodView): decorators = [auth,] methods = ["GET","POST"] def get(self): return "GET" def post(self): return "POST" app.add_url_rule("/index",view_func=MyView.as_view(name="index")) # name ==> endpoint
十,中间件
# 中间件 # Flask 请求的入口 # 1. app.run() ==> run_simple()
# 2. werkzoug的run_simple(host,port,self,**option) # 3. self() --> app() # app() --> Flask.__call__() # 4. __call__ => return self.wsgi_app(*args,**kwargs) # 实现中间件 # 改源码(不推荐..) # 类实现 class Middleware(object) def __init__(self,old_wsgi_app) self.old = old_wsgi_app def __call__(self,*args,**kwargs): # 请求前做某操作 ret self.old(*args,**kwargs) # 请求后做某操作 return ret
# 把Flask().wsgi_app当成蚕食传递给了Middleware
# app.wsgi_app是经过封装的,就是Middleware实例对象 app.wsgi_app = Middleware(app.wsgi_app) app.run() app.__call__() self.wsgi_app(*args,**kwargs) # 实例对象()执行__call__
十一,特殊的装饰器
# 特殊的装饰器 # 注意被装饰器装饰后的函数名问题 @app.before_request # 相当于process_request @app.before_first_request # 只在第一次访问的时候触发 @app.after_request # 相当于process_response @app.template_gloal() # 全局模板替换 {{total(1, 1)}} @app.template_filter() # 类似django的filter {{"hello" | db()}} @app.errorhandler(404) # 当前404时触发 # 注意执行 # before_request有返回值的时候还会按顺序执行after_request # django 的 <=1.9版本 当process_request有返回值的时候,跟flask是一样的
使用自定义装饰器实现 认证功能
import functools def auth(func) @functools.wraps(func) # endpoint 默认为视图名,不修复的话,别名默认都指向inner def inner(*args,**kwargs) return func(*args,**kwargs) return inner
十二,蓝图
主要功能: 做目录结构,解耦 1.新建一个项目目录 项目目录下建一个同名的python包 2.在项目目录下建manager.py
导入create_app
app = create_app()
app.run() 3.在包的__init__实例化Flask对象
def create_app():
把蓝图对象注册到app中
app.register_blueprint(userBlue,**option)
app = Flask(__name__)
return app 4.在manager.py 导入app app.run() 5.在Python包里建立views文件夹 任何一个文件都可以生成蓝图对象 from flask import Blueprint bookBule = BluePrint("bookBlue",__name__) @bookBlue.route("/") def index(): return "xxx"-- python项目目录 -- views目录 -- user.py -- book.py -- 同名的py包 -- __init__ 实例化Flask对象 -- app.py -- manager.py 启动项目 -- 导入app app.run()
十三,Flask的上下文管理(可以简单的理解为一个请求的生命周期)
Flask的上下文管理我们可以简单的理解为一个生命周期,也就是请求进来到请求出去一共做了哪些事情,我们从源码开始走。
准备知识
# 偏函数 from functools import partial 给一个函数固定一个参数 def func(x,y,z): pass new_func = partial(函数名func,固定的参数) new_func(x,y) --> func(固定的参数,x,y) # __setattr__ 对象.xxx --> __getattr__ 对象.xxx = "" --> __setattr__ 当我们实例化的时候先走__init__ 如果在__init__ 执行self.xxx = {} 也会走__setattr__
Flask的上下文管理(源码分析)
# 请求来的时候究竟做了什么? app.run() # 1. 调用了werkzeug中run_simple() self 为app run_simple(host, port, self, **options) # 2. 在run_simple会执行self(),也就是self.__call__(),所以会走Flask的__call__方法 Flask.__call__(): # 2.1 在Flask类的__call__方法执行了wsgi_app()方法 return self.wsgi_app(environ, start_response) # environ是请求的原始数据,start_response为封装的响应对象 # 2.2 在wsgi_app中,将environ作为参数传给了request_context方法 ctx = self.request_context(environ) # 2.2.1 request_context的返回值为RequestContext, ctx被赋值为 RequestContext的实例对象 return RequestContext(self, environ) # self为app,执行RequestContext的__init__方法 # 2.2.2 在RequestContext的__init__方法中,封装了ctx.request和ctx.session,还有ctx.app self.app = app if request is None: # 由于request参数没传值默认为None request = app.request_class(environ) self.request = request # 2.2.3 ctx.session = None self.session = None # request_class 的真身为Request类 request_class = Request # 2.2.4 相对于ctx.request = Request(environ) 被封装为Request的实例对象 # 2.3 ctx继续执行了RequestContext类的push()方法 ctx.push()
在ctx.push()
请求的上下文管理
# 请求上下文管理 # 2.3.1 在push方法中 _request_ctx_stack真身为LocalStack的实例对象 # 相当与执行了 LocalStack().push(ctx) _request_ctx_stack.push(self) # 2.3.1.1 LocalStack类中的__init__方法 self._local = Local() # 2.3.1.2 Local类的中__init__方法, # 封装了 self.__storage__ = {} object.__setattr__(self, '__storage__', {}) # 封装了一个获取线程或协程唯一标识的方法:self.__ident_func__ -> get_ident object.__setattr__(self, '__ident_func__', get_ident) # 2.3.2 LocalStack类中的push方法 def push(self, obj): # obj = ctx # 通过getattr,触发Local类的__getattr__方法 =>return self.__storage__[self.__ident_func__()][name] => name = stack,去__storage__中,唯一标识对应的{stack:[]} rv = getattr(self._local, 'stack', None) if rv is None: # 第一次调用,rv为none # self._local.stack赋值操作触发了local类的__setattr__ # ident = self.__ident_func__() # storage = self.__storage__ # storage[ident][name] = value self._local.stack = rv = [] # 由于rv和self._local.stack都是指向同一个列表内存地址 rv.append(obj) # 相当与在给self._local.__storage__[ident][value].append(ctx) # 优点是步骤小了 return rv
应用的上下文管理
# 应用上下文管理 # 跟请求上下文用的是两个实例化对象 _app_ctx_stack = LocalStack() app_ctx = _app_ctx_stack.top # 也是取 __storage__[唯一标识][stack],没赋值所以为空 if app_ctx is None or app_ctx.app != self.app: # self.app 就是我们Flask实例化对象app # app_context() = AppContext() # AppContext封装了app以及g app_ctx = self.app.app_context() # 调用AppContext里的push方法 # 依然是调用_app_ctx_stack.push方法 # 这里就和请求上下文一样了 # _app_ctx_stack.push(self) # self._local.stack = rv = [] # self._local.__storage__[ident]['stack'].append(ctx) # ident 进程或现线程的唯一标识 app_ctx.push() # 总结,我们请求上下文和应用上下文,分别别建立了两个Local对象,两个Local对象数据结构都是一样的,那么请求上下文和应用上下文为什么要分开存放呢 # 源码注释:在我们推送请求上下文之前,我们必须确保是一个应用程序上下文。
导入的request到时是怎么实现的
# Flask的上下文管理 #在我们的视图中上面那种拿request的方法太费劲了,我们需要简单一点的拿到request~~那么我们导入的request跟我们上面拿到的request一定是一样的~~那导入的这个request到底是如何实现的呢 from flask import request # request是LocalProxy类的实例对象,参数是一个偏函数 request = LocalProxy(partial(_lookup_req_object, 'request')) # _lookup_req_object top = _request_ctx_stack.top # return self._local.stack[-1] # 返回ctx if top is None: raise RuntimeError(_request_ctx_err_msg) return getattr(top, name) # 返回 ctx.request # LocalProxy的实例化 def __init__(self, local, name=None): # name没传 # __slots__ 限制了实例对象的 属性__lacal # self._LocalProxy__local = local => 偏函数 object.__setattr__(self, '_LocalProxy__local', local) # 相当于 self.__local = local # self.__name__ = None object.__setattr__(self, '__name__', name) if callable(local) and not hasattr(local, '__release_local__'): # "local" is a callable that is not an instance of Local or # LocalManager: mark it as a wrapped function. object.__setattr__(self, '__wrapped__', local) # 当我们调用request.method等方法的时候就是走的时LocalProxy这个类的__getattr__方法 return getattr(self._get_current_object(), name) # name => method # _get_currnet_object(),由于是调用类内方法,可以直接使用私有变量,所以可以用self.__local取值 if not hasattr(self.__local, '__release_local__'): return self.__local() # self.__local,引用的就是变形的结果,self._LocalProxy__local ==> 偏函数 return getattr(self.__local, self.__name__) # getattr(self._get_current_object(), name) 相当于去ctx.request.method # 就跟我们上面的取值方式一样了,也就是说通过LocalStack方法去Local中取ctx对象, # 然后通过getattr 找到ctx.request~~~ # 也就是说这个LocalProxy就是一个帮助我们取值的代理~让我们的取值变的更加简单 # 这个代理通过偏函数来绑定参数 # ctx中封装了request,以及session~只不过到这里我们的session依然是空的
session的原理
# Session的实现原理 # 首先请求进来的时候在Local中存放了ctx对象~这个对象里的session是None~~ # 当我们走完这个_reqeust_cts_stack.push(ctx)后,我们看它走了什么 if self.session is None: # session_interface 赋值为 SecureCookieSessionInterface的实例对象 session_interface = self.app.session_interface self.session = session_interface.open_session( self.app, self.request # self=>ctx ) if self.session is None: self.session = session_interface.make_null_session(self.app) # 如果cookie为,session被赋值为一个空字典 # SecureCookieSessionInterface类的open_session方法 def open_session(self, app, request): # s是我们加密解密方法 s = self.get_signing_serializer(app) if s is None: return None # 从cookie中获取数据 val = request.cookies.get(app.session_cookie_name) if not val: return self.session_class() # 获取配置信息的超时时间 max_age = total_seconds(app.permanent_session_lifetime) try: # 解密cookie data = s.loads(val, max_age=max_age) # 把解密号的数据转成字典返回赋值给了ctx.session return self.session_class(data) except BadSignature: return self.session_class() # 请求进来把ctx放入Local中后,从前端解密了cookie,然后把解密数据好的数据给了self.session response = self.full_dispatch_request() # full_dispatch_request() 中执行了finalize_request return self.finalize_request(rv) # finalize_request方法中与session有关的关键代码 response = self.process_response(response) # process_response方法中调用了save_session if not self.session_interface.is_null_session(ctx.session): # 相当于判断前端是否有传cookie self.session_interface.save_session(self, ctx.session, response) # save_session方法执行了设置cookie操作 response.set_cookie( app.session_cookie_name, val, expires=expires, httponly=httponly, domain=domain, path=path, secure=secure, samesite=samesite ) # 总结:从cookie中获取数据~解密存入session,请求走的时候,把session中数据取出来,加密, 给响应设置cookie # 那么我们平时在视图中设置删除session的值~原来跟request是一样的,通过代理去修改Local中的数据
g对象
# 在应用上下文封装了g对象,那么这个g对象到底是什么 请求进来会为每个请求在Local中建立一个独立空间,也就是在应用上下文的Local对象中建立了一个g对象,当请求走的时候,就会删除 g对象一般情况用于before_request中设置值,只为这一次请求建立全局变量 g的生命周期是请求进来到走 # 注意:在我们重定向的时候还可以取g的值 对比session和全局变量 全局变量:是在项目启动创建的,无论多少请求进来都可以访问全局变量 session:保存在cookie中,所以下次请求来的时候cookie中还会带着数据
一个demo
# demo from flask import Flask, request, session, g, current_app from flask.globals import _request_ctx_stack app = Flask(__name__) @app.before_request def auth(): g.xxx = "alex" @app.route("/") def index(): ctx = _request_ctx_stack.top # 取ctx对象 # ctx.request print(ctx.request.method) # 跟request.method一样 print(current_app) # 返回ctx.app 就是当前的app => flask的实例对象 # request.method # request --> LocalProxy(偏函数) # request.xxx --> LocalProxy __getattr__ # __getattr__ --> getattr(偏函数的执行,xxx ) # 偏函数--> _request_ctx_stack.top.request # g.xxx = "nezha" print(g.xxx) return "INDEX" @app.route("/user") def user(): # print(g.xxx) return "USER~~" if __name__ == '__main__': app.run()