Flask

一,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()
相關文章
相關標籤/搜索