超實用的Flask入門基礎教程,新手必備!
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=Login> </form> ''' @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 應用了嗎?你可以立即部署到付費的或者免費的伺服器來完成快速入門。
&n