Flask 快速上手
一個最小的應用
一個最小的 Flask 應用如下:
from flask import Flask app = Flask(__name__)@app.route('/') def hello_world():return 'Hello, World!'那么,這些代碼是什么意思呢?
把它保存為 hello.py 或其他類似名稱。請不要使用 flask.py 作為應用名稱,這會與 Flask 本身發生沖突。
這樣就啟動了一個非常簡單的內建的服務器。這個服務器用于測試應該是足夠了,但是 用于生產可能是不夠的。
現在在瀏覽器中打開 http://127.0.0.1:5000/ ,應該可以看到 Hello World! 字樣。
路由
現代 web 應用都使用有意義的 URL ,這樣有助于用戶記憶,網頁會更得到用戶的青睞, 提高回頭率。
使用 route() 裝飾器來把函數綁定到 URL:
@app.route('/') def index():return 'Index Page'@app.route('/hello') def hello():return 'Hello, World'但是能做的不僅僅是這些!你可以動態變化 URL 的某些部分, 還可以為一個函數指定多個規則。
變量規則
通過把 URL 的一部分標記為 <variable_name> 就可以在 URL 中添加變量。標記的 部分會作為關鍵字參數傳遞給函數。通過使用 <converter:variable_name> ,可以 選擇性的加上一個轉換器,為變量指定規則。請看下面的例子:
@app.route('/user/<username>') def show_user_profile(username):# show the user profile for that userreturn '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 integerreturn '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 / 重定向行為
以下兩條規則的不同之處在于是否使用尾部的斜杠。:
@app.route('/projects/') def projects():return 'The project page'@app.route('/about') def about():return 'The about page'projects 的 URL 是中規中矩的,尾部有一個斜杠,看起來就如同一個文件夾。 訪問一個沒有斜杠結尾的 URL 時 Flask 會自動進行重定向,幫你在尾部加上一個斜杠。
about 的 URL 沒有尾部斜杠,因此其行為表現與一個文件類似。如果訪問這個 URL 時添加了尾部斜杠就會得到一個 404 錯誤。這樣可以保持 URL 唯一,并幫助 搜索引擎避免重復索引同一頁面。
URL 構建
url_for() 函數用于構建指定函數的 URL。它把函數名稱作為第一個 參數。它可以接受任意個關鍵字參數,每個關鍵字參數對應 URL 中的變量。未知變量 將添加到 URL 中作為查詢參數。
為什么不在把 URL 寫死在模板中,而要使用反轉函數 url_for() 動態構建?
例如,這里我們使用 test_request_context() 方法來嘗試使用 url_for() 。 test_request_context() 告訴 Flask 正在處理一個請求,而實際上也許我們正處在交互 Python shell 之中, 并沒有真正的請求。
from flask import Flask, escape, url_forapp = 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'))print(url_for('login', next='/'))print(url_for('profile', username='John Doe')) / /login /login?next=/ /user/John%20DoeHTTP 方法
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 也會自動實現。
靜態文件
動態的 web 應用也需要靜態文件,一般是 CSS 和 JavaScript 文件。理想情況下你的 服務器已經配置好了提供靜態文件的服務。但是在開發過程中, Flask 也能做好 這項工作。只要在你的包或模塊旁邊創建一個名為 static 的文件夾就行了。 靜態文件位于應用的 /static 中。
使用特定的 ‘static’ 端點就可以生成相應的 URL
url_for('static', filename='style.css')這個靜態文件在文件系統中的位置應該是 static/style.css 。
渲染模板
在 Python 內部生成 HTML 不好玩,且相當笨拙。因為你必須自己負責 HTML 轉義, 以確保應用的安全。因此, Flask 自動為你配置 Jinja2 模板引擎。
使用 render_template() 方法可以渲染模板,你只要提供模板名稱和需要 作為參數傳遞給模板的變量就行了。下面是一個簡單的模板渲染例子:
from flask import render_template@app.route('/hello/') @app.route('/hello/<name>') def hello(name=None):return render_template('hello.html', name=name)Flask 會在 templates 文件夾內尋找模板。因此,如果你的應用是一個模塊, 那么模板文件夾應該在模塊旁邊;如果是一個包,那么就應該在包里面:
情形 1 : 一個模塊:
情形 2 : 一個包:
/application/__init__.py/templates/hello.html你可以充分使用 Jinja2 模板引擎的威力。
模板示例:
<!doctype html> <title>Hello from Flask</title> {% if name %}<h1>Hello {{ name }}!</h1> {% else %}<h1>Hello, World!</h1> {% endif %}在模板內部可以和訪問 get_flashed_messages() 函數一樣訪問 request 、 session 對象。
模板在繼承使用的情況下尤其有用。
簡單的說,模板繼承可以使每個頁面的特定元素(如頁頭、導航和頁尾) 保持一致。
自動轉義默認開啟。因此,如果 name 包含 HTML ,那么會被自動轉義。如果你可以 信任某個變量,且知道它是安全的 HTML (例如變量來自一個把 wiki 標記轉換為 HTML 的模塊),那么可以使用 Markup 類把它標記為安全的,或者在模板 中使用 |safe 過濾器。
操作請求數據
對于 web 應用來說對客戶端向服務器發送的數據作出響應很重要。在 Flask 中由全局 對象 request 來提供請求信息。如果你有一些 Python 基礎,那么 可能 會奇怪:既然這個對象是全局的,怎么還能保持線程安全?答案是本地環境:
本地環境
某些對象在 Flask 中是全局對象,但不是通常意義下的全局對象。這些對象實際上是 特定環境下本地對象的代理。真拗口!但還是很容易理解的。
設想現在處于處理線程的環境中。一個請求進來了,服務器決定生成一個新線程(或者 叫其他什么名稱的東西,這個下層的東西能夠處理包括線程在內的并發系統)。當 Flask 開始其內部請求處理時會把當前線程作為活動環境,并把當前應用和 WSGI 環境綁定到 這個環境(線程)。它以一種聰明的方式使得一個應用可以在不中斷的情況下調用另一個 應用。
這對你有什么用?基本上你可以完全不必理會。這個只有在做單元測試時才有用。在測試 時會遇到由于沒有請求對象而導致依賴于請求的代碼會突然崩潰的情況。對策是自己創建 一個請求對象并綁定到環境。最簡單的單元測試解決方案是使用 test_request_context() 環境管理器。通過使用 with 語句 可以綁定一個測試請求,以便于交互。例如:
from flask import requestwith app.test_request_context('/hello', method='POST'):# now you can do something with the request until the# end of the with block, such as basic assertions:assert request.path == '/hello'assert request.method == 'POST'另一種方式是把整個 WSGI 環境傳遞給 request_context() 方法:
from flask import requestwith app.request_context(environ):assert request.method == 'POST'請求對象
首先,你必須從 flask 模塊導入請求對象:
from flask import request通過使用 method 屬性可以操作當前請求方法,通過使用 form 屬性處理表單數據(在 POST 或者 PUT 請求 中傳輸的數據)。以下是使用上述兩個屬性的例子:
@app.route('/login', methods=['POST', 'GET']) def login():error = Noneif 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'# the code below is executed if the request method# was GET or the credentials were invalidreturn 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 ,這表示頁面對就的出錯 代碼是 404 ,即頁面不存在。缺省情況下 200 表示:一切正常。
關于響應
視圖函數的返回值會自動轉換為一個響應對象。如果返回值是一個字符串,那么會被 轉換為一個包含作為響應體的字符串、一個 200 OK 出錯代碼 和一個 text/html 類型的響應對象。如果返回值是一個字典,那么會調用 jsonify() 來產生一個響應。以下是轉換的規則:
如果想要在視圖內部掌控響應對象的結果,那么可以使用 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 respJSON 格式的 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, requestapp = Flask(__name__)# Set the secret key to some random bytes. Keep this really secret! 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 theresession.pop('username', None)return redirect(url_for('index'))這里用到的 escape() 是用來轉義的。如果不使用模板引擎就可以像上例 一樣使用這個函數來轉義。
如何生成一個好的密鑰
生成隨機數的關鍵在于一個好的隨機種子,因此一個好的密鑰應當有足夠的隨機性。 操作系統可以有多種方式基于密碼隨機生成器來生成隨機數據。使用下面的命令 可以快捷的為 Flask.secret_key ( 或者 SECRET_KEY )生成值:
$ python -c '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')logger 是一個標準的 Logger Logger 類。
總結
以上是生活随笔為你收集整理的Flask 快速上手的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安装 Flask
- 下一篇: Flask 项目布局