一,Flask初識html
Python現階段三大主流web框架Django,Tornad,Flask對比前端
1.Django主要特色是大而全,集成了不少組件,例如:Models Admin Form等等,不論是否用上,它全都有,屬於全能型框架,Django一般用於大型web應用因爲內置組件足夠強大因此使用Django開發能夠一鼓作氣,缺點是浪費資源,一次性加載所有資源,確定會形成一部分資源的浪費想·python
2.Tornado主要特色是原生異步非阻塞,在IO密集型應用和多任務處理上佔據絕對性的優點,屬於專一型框架,一般用於api後端應用,遊戲服務後臺,內部實現的異步非阻塞很是牛逼,缺點是乾淨,不支持Sessionweb
3.Flask主要特色小而輕,原生組件幾乎爲0,三方提供的組件請參考django很是全面,屬於短小精悍型框架,一般應用於小型應用和快速構建應用,其強大的三方庫,足以支撐一個大型的web應用正則表達式
Flask 安裝django
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的demoflask
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的路由api
# 通常的路由 @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()