超實用的Flask入門基礎教程,新手必備!
- 2020 年 3 月 26 日
- 筆記
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_nameScriptsactivate
安裝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"F4Q8znxec]/' @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"F4Q8znxec]/'
基於 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 應用了嗎?你可以立即部署到付費的或者免費的伺服器來完成快速入門。