Flask入門基礎教程
Flask簡介
Flask是一個輕量級的可定制框架,使用Python語言撰寫,較其他同型別框架更為靈活、輕便、安全且容易上手,它可以很好地結合MVC模式進行開發,開發人員分工合作,小型團隊在短時間內就可以完成功能豐富的中小型網站或Web服務的實作,另外,Flask還有很強的定制性,用戶可以根據自己的需求來添加相應的功能,在保持核心功能簡單的同時實作功能的豐富與擴展,其強大的插件庫可以讓用戶實作個性化的網站定制,開發出功能強大的網站,
安裝Flask
依賴
當安裝 Flask 時,以下配套軟體會被自動安裝:
> - Werkzeug 用于實作 WSGI 是一個 WSGI(在 Web 應用和多種服務器之間的標準 Python 介面) 工具集,
> - jinja2是Python的一個流行的模板引擎,Web模板系統將模板與特定資料源組合以呈現動態網頁,
> - MarkupSafe 與 Jinja 共用,在渲染頁面時用于避免不可信的輸入,防止注入攻擊,
> - ItsDangerous 保證資料完整性的安全標志資料,用于保護 Flask 的 session cookie.
> - Click 是一個命令列應用的框架,用于提供 flask 命令,并允許添加自定義 管理命令,
創建虛擬環境
創建檔案夾,在檔案夾下面 輸入命令
python -m venv venv_name
激活虛擬環境
激活這個虛擬環境(注意,使用的是虛擬環境的話前面會有(venv_name)這個顯示的,不然就是沒有激活虛擬環境,)
venv_name\Scripts\activate
安裝Flask
在已激活的虛擬環境中使用pip安裝Flask
pip install Flask
基礎介紹
在Flask中,最基礎的一個功能是這樣子的
from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello, World!' if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=5000)
- 首先,我們導入了Flask類,
- 其次我們創建了Flask的實體,第一個引數是應用模塊或者包的名稱, 如果你使用單一的模塊(如本例),你應該使用 __name__ ,因為模塊的名稱將會因其作為單獨應用啟動還是作為模塊匯入而有不同( 也即是 '__main__' 或實際的匯入名),這是必須的,這樣 Flask 才知道到哪去找模板、靜態檔案等等,
- route()是一個路由,其實是一個裝飾器,在其中輸入URL,會幫我們在這個URL下執行對應的方法,
- 接著是函式主體,可以寫方法也可以呼叫其他方法的回傳值,最后回傳到瀏覽器上顯示的資訊
- 最后我們用 run() 函式來讓應用運行在本地服務器上, 其中 if __name__ =='__main__': 確保服務器只會在該腳本被 Python 解釋器直接執行的時候才會運行,而不是作為模塊匯入的時候,debug=True開啟了除錯模式,相當于在發生錯誤時提供一個相當有用的除錯器,host=’0.0.0.0‘可以允許同一個局域網內別的用戶訪問,這個方法讓作業系統監聽所有公網 IP,port自定義埠,
路由
現代Web框架使用路由技術來幫助用戶記住應用程式URL,可以直接訪問所需的頁面,而無需從主頁導航,Flask中的route()裝飾器用于將URL系結到函式,例如:
@app.route('/index') def index(): return 'This is a index page...'
在這里,URL '/ index' 規則系結到index()函式, 因此,如果用戶訪問127.0.0.1:5000/index,index()函式的輸出將在瀏覽器中呈現,
變數規則
通過把 URL 的一部分標記為 <variable_name> 就可以在 URL 中添加變數,標記的 部分會作為關鍵字引數傳遞給函式,通過使用 <converter:variable_name> ,可以 選擇性的加上一個轉換器,為變數指定規則,請看下面的例子:
@app.route('/user/<username>') def show_user_profile(username): # show the user profile for that user return 'User %s' % escape(username) @app.route('/post/<int:post_id>') def show_post(post_id): # show the post with the given id, the id is an integer return 'Post %d' % post_id @app.route('/path/<path:subpath>') def show_subpath(subpath): # show the subpath after /path/ return 'Subpath %s' % escape(subpath)
轉換器型別:
| 型別 | 說明 |
| string | (預設值) 接受任何不包含斜杠的文本 |
| int | 接受正整數 |
| float | 接受正浮點數 |
| path | 類似string,但可以包含斜杠 |
| uuid | 接受UUID字串 |
唯一 URL / 重定向行為
Flask的URL規則是基于Werkzeug的路由模塊,模塊背后的思想是基于 Apache 以及更早的 HTTP 服務器主張的先例,保證優雅且唯一的 URL,
@app.route('/projects/') def projects(): return 'The project page' @app.route('/about') def about(): return 'The about page'
訪問第一個路由不帶/時,Flask會自動重定向到正確地址,
訪問第二個路由時末尾帶上/后Flask會直接報404 NOT FOUND錯誤,
永久性重定向和暫時性重定向
flask是通過flask.redirect(location,code=302)這個函式來實作重定向的,location是需要重定向到的url,應該配合之前講的在url_for()函式來使用,code表示哪種重定向,默認302,也即暫時性重定向,301是永久性重定向.
構建URL
如果 Flask 能匹配 URL,那么 Flask 可以生成它們嗎?當然可以,你可以用 url_for()來給指定的函式構造 URL,它接受函式名作為第一個引數,也接受對應 URL 規則的變數部分的命名引數,未知變數部分會添加到 URL 末尾作為查詢引數,
例如,這里我們使用 test_request_context() 方法來嘗試使用 url_for() , test_request_context() 告訴 Flask 正在處理一個請求,而實際上也許我們正處在互動 Python shell 之中, 并沒有真正的請求,
from flask import Flask, url_for app = Flask(__name__) @app.route('/') def index(): return 'index' @app.route('/login') def login(): return 'login' @app.route('/user/<username>') def profile(username): return '{}\'s profile'.format(escape(username)) with app.test_request_context(): print(url_for('index')) #輸出 / print(url_for('login')) #輸出 /login print(url_for('login', next='/')) #輸出 /login?next=/ print(url_for('profile', username='John Doe')) #輸出 /user/John%20Doe
那么為什么不在把 URL 寫死在模板中,而要使用反轉函式 url_for() 動態構建?
- 反轉通常比硬編碼 URL 的描述性更好,
- 你可以只在一個地方改變 URL ,而不用到處亂找,
- URL 創建會為你處理特殊字符的轉義和 Unicode 資料,比較直觀,
- 生產的路徑總是絕對路徑,可以避免相對路徑產生副作用,
- 如果你的應用是放在 URL 根路徑之外的地方(如在 /myapplication 中,不在 / 中), url_for() 會為你妥善處理,
HTTP方法
Web 應用使用不同的 HTTP 方法處理 URL ,當你使用 Flask 時,應當熟悉 HTTP 方法, 預設情況下,一個路由只回應 GET 請求, 可以使用 route() 裝飾器的 methods 引數來處理不同的 HTTP 方法:
from flask import request @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': return do_the_login() else: return show_the_login_form()如果當前使用了 GET 方法, Flask 會自動添加 HEAD 方法支持,并且同時還會 按照 HTTP RFC 來處理 HEAD 請求,同樣, OPTIONS 也會自動實作,
HTTP 方法(也經常被叫做“謂詞”)告知服務器,客戶端想對請求的頁面 做 些什么,下面的都是非常常見的方法:
- GET:瀏覽器告知服務器:只 獲取 頁面上的資訊并發給我,這是最常用的方法,
- HEAD:瀏覽器告訴服務器:欲獲取資訊,但是只關心 訊息頭,應用應像處理 GET 請求一樣來處理它,但是不分發實際內容,在 Flask 中你完全無需 人工 干預,底層的 Werkzeug 庫已經替你打點好了,
- POST:瀏覽器告訴服務器:想在 URL 上 發布 新資訊,并且,服務器必須確保 資料已存盤且僅存盤一次,這是HTML 表單通常發送資料到服務器的方法,
- PUT:類似 POST 但是服務器可能觸發了存盤程序多次,多次覆寫掉舊值,你可能會問這有什么用,當然這是有原因的,考慮到傳輸中連接可能會丟失,在 這種
- 情況下瀏覽器和服務器之間的系統可能安全地第二次接收請求,而不破壞其它東西,因為 POST它只觸發一次,所以用 POST是不可能的,
- DELETE:洗掉給定位置的資訊,
- OPTIONS:給客戶端提供一個敏捷的途徑來弄清這個 URL 支持哪些 HTTP 方法,從 Flask 0.6 開始,實作了自動處理,
Request物件
from flask import Flask,jsonify from flask import request @app.route('/api/add', methods=['POST']) def add_elasticsearch(): city_name = request.form.get('city_name') diagnose_people = request.form.get('diagnose_people') suspect_people = request.form.get('suspect_people') death_people = request.form.get('death_people') cure_people = request.form.get('cure_people') result = main.FuncUtil.add_es(city_name, diagnose_people, suspect_people, death_people, cure_people) return jsonify(result)
request中”method”變數可以獲取當前請求的方法,即”GET”, “POST”, “DELETE”, “PUT”等,”form”變數是一個字典,可以獲取Post請求表單中的內容,如果提交的表單中不存在,則會回傳一個”KeyError”,你可以不捕獲,頁面會回傳400錯誤(想避免拋出這”KeyError”,你可以用request.form.get(“user”)來替代),而”request.args.get()”方法則可以獲取Get請求URL中的引數,該函式的第二個引數是默認值,當URL引數不存在時,則回傳默認值,在后文的請求物件會講到,
靜態檔案
動態 web 應用也會需要靜態檔案,通常是 CSS 和 JavaScript 檔案,理想狀況下, 你已經配置好 Web 服務器來提供靜態檔案,但是在開發中,Flask 也可以做到, 只要在你的包中或是模塊的所在目錄中創建一個名為 static 的檔案夾,在應用中使用 /static 即可訪問,
給靜態檔案生成 URL ,使用特殊的 'static' 端點名:
url_for('static', filename='style.css')
這個檔案應該存盤在檔案系統上的 static/style.css ,
模板渲染
Flask的模板功能是基于Jinja2模板引擎實作的,讓我們來實作一個例子,修改之前的Flask運行檔案,代碼如下:
from flask import Flask,render_template app = Flask(__name__) @app.route('/hello/<name>') def hello_world(name=None): return render_template('hello.html', name=name) if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=5000)
這段代碼”hello()”函式并不是直接回傳字串,而是呼叫了”render_template()”方法來渲染模板,方法的第一個引數”hello.html”指向你想渲染的模板名稱,第二個引數”name”是你要傳到模板去的變數,變數可以傳多個,接下來我們創建模板檔案,在當前目錄下,創建一個子目錄”templates”(注意,一定要使用這個名字),然后在”templates”目錄下創建檔案”hello.html”,內容如下:
<!doctype html> <title>Hello Reader</title> {% if name %} <h1>Hello {{ name }}!</h1> {% else %} <h1>Hello World!</h1> {% endif %}
這是一個HTML模板,根據”name”變數的值,顯示不同的內容,變數或運算式由”{{ }}”修飾,而控制陳述句由”{% %}”修飾,其他的代碼,就是我們常見的HTML,打開瀏覽器,輸入”http://127.0.0.1:5000/hello/Reader”,頁面上即顯示大標題”Hello Reader !”,
模板繼承
一般我們的網站雖然頁面多,但是很多部分是重用的,比如頁首,頁腳,導航欄之類的,對于每個頁面,都要寫這些代碼,很麻煩,Flask的Jinja2模板支持模板繼承功能,省去了這些重復代碼,讓我們基于上面的例子,在”templates”目錄下,創建一個名為”layout.html”的模板:
<!doctype html> <title>Hello xxx</title> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}"> <div class="page"> {% block body %} {% endblock %} </div>
再修改之前的”hello.html”,把原來的代碼定義在”block body”中,并在代碼一開始”繼承”上面的”layout.html”:
{% extends "layout.html" %} {% block body %} {% if name %} <h1>Hello {{ name }}!</h1> {% else %} <h1>Hello World!</h1> {% endif %} {% endblock %}
打開瀏覽器,再看下”http://127.0.0.1:5000/hello/Reader”頁面的原始碼,
<!doctype html> <title>Hello xxx</title> <link rel="stylesheet" type="text/css" href="/static/style.css"> <div class="page"> <h1>Hello Reader!</h1> </div>
你會發現,雖然”render_template()”加載了”hello.html”模板,但是”layout.html”的內容也一起被加載了,而且”hello.html”中的內容被放置在”layout.html”中”{% block body %}”的位置上,形象的說,就是”hello.html”繼承了”layout.html”,
訪問請求資料
對于 Web 應用,與客戶端發送給服務器的資料互動至關重要,在 Flask 中由全域的 request 物件來提供這些資訊,如果你有一定的 Python 經驗,你會好奇,為什么這個物件是全域的,為什么 Flask 還能保證執行緒安全,答案是本地環境,
本地環境
Flask 中的某些物件是全域物件,但卻不是通常的那種,這些物件實際上是特定環境的區域物件的代理,雖然很拗口,但實際上很容易理解,
想象一下處理執行緒的環境,一個請求傳入,Web 服務器決定生成一個新執行緒( 或者別的什么東西,只要這個底層的物件可以勝任并發系統,而不僅僅是執行緒), 當 Flask 開始它內部的請求處理時,它認定當前執行緒是活動的環境,并系結當前的應用和 WSGI 環境到那個環境上(執行緒),它的實作很巧妙,能保證一個應用呼叫另一個應用時不會出現問題,
所以,這對你來說意味著什么?除非你要做類似單元測驗的東西,否則你基本上可以完全無視它,你會發現依賴于一段請求物件的代碼,因沒有請求物件無法正常運行,解決方案是,自行創建一個請求物件并且把它系結到環境中,單元測驗的最簡單的解決方案是:用 test_request_context() 環境管理器,結合 with 宣告,系結一個測驗請求,這樣你才能與之互動,下面是一個例子:
from flask import request with app.test_request_context('/hello', method='POST'): # 現在,你可以對請求執行某些操作,直到with塊結束為止,例如基本斷言: assert request.path == '/hello' assert request.method == 'POST'
另一種可能是:傳遞整個 WSGI 環境給 request_context() 方法:
from flask import request with app.request_context(environ): assert request.method == 'POST'
請求物件
通過使用 method 屬性可以操作當前請求方法,通過使用 form 屬性處理表單資料(在 POST 或者 PUT 請求 中傳輸的資料),以下是使用上述兩個屬性的例子:
from flask import render_template @app.route('/login', methods=['POST', 'GET']) def login(): error = None if request.method == 'POST': if valid_login(request.form['username'], request.form['password']): return log_the_user_in(request.form['username']) else: error = 'Invalid username/password' #如果請求方法為GET或憑據無效,則執行以下代碼 return render_template('login.html', error=error)
當 form 屬性中不存在這個鍵時會發生什么?會引發一個 KeyError , 如果你不像捕捉一個標準錯誤一樣捕捉 KeyError ,那么會顯示一個 HTTP 400 Bad Request 錯誤頁面,因此,多數情況下你不必處理這個問題,
要操作 URL (如 ?key=value )中提交的引數可以使用 args 屬性:
searchword = request.args.get('key', '')
用戶可能會改變 URL 導致出現一個 400 請求出錯頁面,這樣降低了用戶友好度,因此, 我們推薦使用 get 或通過捕捉 KeyError 來訪問 URL 引數,
檔案上傳
用 Flask 處理檔案上傳很容易,只要確保不要忘記在你的 HTML 表單中設定 enctype="multipart/form-data" 屬性就可以了,否則瀏覽器將不會傳送你的檔案,
已上傳的檔案被儲存在記憶體或檔案系統的臨時位置,你可以通過請求物件 files 屬性來訪問上傳的檔案,每個上傳的檔案都儲存在這個 字典型屬性中,這個屬性基本和標準 Python file 物件一樣,另外多出一個 用于把上傳檔案保存到服務器的檔案系統中的 save() 方法,下例展示其如何運作:
from flask import request @app.route('/upload', methods=['GET', 'POST']) def upload_file(): if request.method == 'POST': f = request.files['the_file'] f.save('/var/www/uploads/uploaded_file.txt')
如果想要知道檔案上傳之前其在客戶端系統中的名稱,可以使用 filename 屬性,但是請牢記這個值是 可以偽造的,永遠不要信任這個值,如果想要把客戶端的檔案名作為服務器上的檔案名, 可以通過 Werkzeug 提供的 secure_filename() 函式:
from flask import request from werkzeug.utils import secure_filename @app.route('/upload', methods=['GET', 'POST']) def upload_file(): if request.method == 'POST': f = request.files['the_file'] f.save('/var/www/uploads/' + secure_filename(f.filename))
Cookies
要訪問 cookies ,可以使用 cookies 屬性,可以使用回應 物件 的 set_cookie 方法來設定 cookies ,請求物件的 cookies 屬性是一個包含了客戶端傳輸的所有 cookies 的字典,在 Flask 中,如果使用 會話 ,那么就不要直接使用 cookies ,因為 會話 比較安全一些,
讀取 cookies:
from flask import request @app.route('/') def index(): username = request.cookies.get('username') # use cookies.get(key) instead of cookies[key] to not get a # KeyError if the cookie is missing.
儲存 cookies:
from flask import make_response @app.route('/') def index(): resp = make_response(render_template(...)) resp.set_cookie('username', 'the username') return resp
注意, cookies 設定在回應物件上,通常只是從視圖函式回傳字串, Flask 會把它們 轉換為回應物件,如果你想顯式地轉換,那么可以使用 make_response() 函式,然后再修改它,
使用 延遲的請求回呼 方案可以在沒有回應物件的情況下設定一個 cookie ,
重定向和錯誤
你可以用 redirect() 函式把用戶重定向到其它地方,放棄請求并回傳錯誤代碼,用 abort() 函式,這里是一個它們如何使用的例子:
from flask import abort, redirect, url_for @app.route('/') def index(): return redirect(url_for('login')) @app.route('/login') def login(): abort(401) this_is_never_executed()
這是一個相當無意義的例子因為用戶會從主頁重定向到一個不能訪問的頁面 (401 意味著禁止訪問),但是它展示了重定向是如何作業的,
默認情況下,錯誤代碼會顯示一個黑白的錯誤頁面,如果你要定制錯誤頁面, 可以使用 errorhandler() 裝飾器:
from flask import render_template @app.errorhandler(404) def page_not_found(error): return render_template('page_not_found.html'), 404
注意 render_template() 呼叫之后的 404 ,這告訴 Flask,該頁的錯誤代碼是 404 ,即沒有找到,默認為 200,也就是一切正常,
回應
視圖函式的回傳值會被自動轉換為一個回應物件,如果回傳值是一個字串, 它被轉換為該字串為主體的、狀態碼為 200 OK的 、 MIME 型別是text/html 的回應物件,Flask 把回傳值轉換為回應物件的邏輯是這樣:
> 1. 如果回傳的是一個合法的回應物件,它會從視圖直接回傳,
> 2. 如果回傳的是一個字串,回應物件會用字串資料和默認引數創建,
> 3. 如果回傳的是一個字典,那么呼叫 jsonify 創建一個回應物件,
> 4. 如果回傳的是一個元組,且元組中的元素可以提供額外的資訊,這樣的元組必須是(response, status, headers) 的形式,且至少包含一個元素, status 值會覆寫狀態代碼, headers可以是一個串列或字典,作為額外的訊息標頭值,
> 5. 如果上述條件均不滿足, Flask 會假設回傳值是一個合法的 WSGI應用程式,并轉換為一個請求物件, 如果你想在視圖里操縱上述步驟結果的回應物件,可以使用 make_response() 函式,
譬如你有這樣一個視圖:
@app.errorhandler(404) def not_found(error): return render_template('error.html'), 404
你只需要把回傳值運算式傳遞給 make_response() ,獲取結果物件并修改,然后再回傳它:
@app.errorhandler(404) def not_found(error): resp = make_response(render_template('error.html'), 404) resp.headers['X-Something'] = 'A value' return resp
JSON 格式的 API
JSON 格式的回應是常見的,用 Flask 寫這樣的 API 是很容易上手的,如果從視圖 回傳一個 dict ,那么它會被轉換為一個 JSON 回應,
@app.route("/me") def me_api(): user = get_current_user() return { "username": user.username, "theme": user.theme, "image": url_for("user_image", filename=user.image), }
如果 dict 還不能滿足需求,還需要創建其他型別的 JSON 格式回應,可以使用 jsonify() 函式,該函式會序列化任何支持的 JSON 資料型別, 也可以研究研究 Flask 社區擴展,以支持更復雜的應用,
@app.route("/users") def users_api(): users = get_all_users() return jsonify([user.to_json() for user in users])
會話
除了請求物件之外還有一種稱為 session 的物件,允許你在不同請求 之間儲存資訊,這個物件相當于用密鑰簽名加密的 cookie ,即用戶可以查看你的 cookie ,但是如果沒有密鑰就無法修改它,
使用會話之前你必須設定一個密鑰,舉例說明:
from flask import Flask, session, redirect, url_for, escape, request app = Flask(__name__) #設定一個隨機密鑰 app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' @app.route('/') def index(): if 'username' in session: return 'Logged in as %s' % escape(session['username']) return 'You are not logged in' @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': session['username'] = request.form['username'] return redirect(url_for('index')) return ''' <form method="post"> <p><input type=text name=username> <p><input type=submit value=https://www.cnblogs.com/Zhengnengjin/p/Login> ''' @app.route('/logout') def logout(): # remove the username from the session if it's there session.pop('username', None) return redirect(url_for('index'))
這里用到的 escape() 是用來轉義的,如果不使用模板引擎就可以像上例 一樣使用這個函式來轉義,
如何生成一個好的密鑰
生成亂數的關鍵在于一個好的隨機種子,因此一個好的密鑰應當有足夠的隨機性, 作業系統可以有多種方式基于密碼隨機生成器來生成隨機資料,使用下面的命令 可以快捷的為 Flask.secret_key ( 或者 SECRET_KEY )生成值:
import os print(os.urandom(16)) #b'_5#y2L"F4Q8z\n\xec]/'
基于 cookie 的會話的說明: Flask 會取出會話物件中的值,把值序列化后儲存到 cookie 中,在打開 cookie 的情況下,如果需要查找某個值,但是這個值在請求中 沒有持續儲存的話,那么不會得到一個清晰的出錯資訊,請檢查頁面回應中的 cookie 的大小是否與網路瀏覽器所支持的大小一致,
除了預設的客戶端會話之外,還有許多 Flask 擴展支持服務端會話,
訊息閃現
一個好的應用和用戶介面都有良好的反饋,否則到后來用戶就會討厭這個應用, Flask 通過閃現系統來提供了一個易用的反饋方式,閃現系統的基本作業原理是在請求結束時 記錄一個訊息,提供且只提供給下一個請求使用,通常通過一個布局模板來展現閃現的 訊息,
flash() 用于閃現一個訊息,在模板中,使用 get_flashed_messages() 來操作訊息
日志
有時候可能會遇到資料出錯需要糾正的情況,例如因為用戶篡改了資料或客戶端代碼出錯 而導致一個客戶端代碼向服務器發送了明顯錯誤的 HTTP 請求,多數時候在類似情況下 回傳 400 Bad Request 就沒事了,但也有不會回傳的時候,而代碼還得繼續運行下去,
這時候就需要使用日志來記錄這些不正常的東西了,自從 Flask 0.3 后就已經為你配置好 了一個日志工具,
以下是一些日志呼叫示例:
app.logger.debug('A value for debugging') app.logger.warning('A warning occurred (%d apples)', 42) app.logger.error('An error occurred')
部署到 Web 服務器
準備好部署你的 Flask 應用了嗎?你可以立即部署到付費的或者免費的服務器來完成快速入門,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/165607.html
標籤:Python
