什么是Flask
Flask,Python开发中的后端框架。
在Python开发中,另一个常见的后端框架是Django。
Django是个大而全的框架。
内部有非常多的组件:orm、session、cookie、admin、form、modelform、路由、视图、模板,中间件、分页、auth、contenttvpe、缓存、信号、多数据库连接等。
flask是一个轻量级的框架。
框架本身没有太多的功能,只有:路由、视图、模板(jinja2)、session、中间件。但是,第三方组件非常齐全。
在请求处理方面,Django的请求处理是逐一封装和传递,Flask的请求处理是利用上下文管理来实现的。在下文我们会看到具体的体现。
安装
安装命令:pip install flask
示例代码:
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Looking in indexes: https://mirrors.aliyun.com/pypi/simple/ Collecting flask Downloading https://mirrors.aliyun.com/pypi/packages/36/42/015c23096649b908c809c69388a805a571a3bea44362fe87e33fc3afa01f/flask-3.0.0-py3-none-any.whl (99 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 99.7/99.7 kB 696.5 kB/s eta 0:00:00 Collecting click>=8.1.3 Downloading https://mirrors.aliyun.com/pypi/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl (97 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 97.9/97.9 kB 702.7 kB/s eta 0:00:00 Collecting Jinja2>=3.1.2 Downloading https://mirrors.aliyun.com/pypi/packages/bc/c3/f068337a370801f372f2f8f6bad74a5c140f6fda3d9de154052708dd3c65/Jinja2-3.1.2-py3-none-any.whl (133 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 133.1/133.1 kB 701.8 kB/s eta 0:00:00 Collecting itsdangerous>=2.1.2 Downloading https://mirrors.aliyun.com/pypi/packages/68/5f/447e04e828f47465eeab35b5d408b7ebaaaee207f48b7136c5a7267a30ae/itsdangerous-2.1.2-py3-none-any.whl (15 kB) Collecting Werkzeug>=3.0.0 Downloading https://mirrors.aliyun.com/pypi/packages/c3/fc/254c3e9b5feb89ff5b9076a23218dafbc99c96ac5941e900b71206e6313b/werkzeug-3.0.1-py3-none-any.whl (226 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 226.7/226.7 kB 696.2 kB/s eta 0:00:00 Collecting blinker>=1.6.2 Downloading https://mirrors.aliyun.decom/pypi/packages/bf/2b/11bcedb7dee4923253a4a21bae3be854bcc4f06295bd827756352016d97c/blinker-1.6.3-py3-none-any.whl (13 kB) Collecting MarkupSafe>=2.0 Downloading https://mirrors.aliyun.com/pypi/packages/12/b3/d9ed2c0971e1435b8a62354b18d3060b66c8cb1d368399ec0b9baa7c0ee5/MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (25 kB) Installing collected packages: MarkupSafe, itsdangerous, click, blinker, Werkzeug, Jinja2, flask Successfully installed Jinja2-3.1.2 MarkupSafe-2.1.3 Werkzeug-3.0.1 blinker-1.6.3 click-8.1.7 flask-3.0.0 itsdangerous-2.1.2
注意安装过程,有两个依赖:
Jinja2
提供模板功能
Werkzeug
提供wsgi的功能,Web Server Gateway Interface,Web服务网关接口。
入门案例
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 from flask import Flaskapp = Flask(__name__) @app.route('/index') def index () : return 'index' if __name__ == '__main__' : app.run()
运行结果:
1 2 3 4 5 * Serving Flask app 'ft1' * Debug mode: off WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on http://127.0.0.1:5000 Press CTRL+C to quit
然后,我们访问http://127.0.0.1:5000/index
Werkzeug
如果我们点进app.run()
方法,会在run
方法中,找到这么一段代码:
1 2 3 4 5 6 7 8 9 from werkzeug.serving import run_simpletry : run_simple(t.cast(str, host), port, self, **options) finally : self._got_first_request = False
这就是Werkzeug在Flask中发挥的作用。
我们可以直接基于Werkzeug
实现一个Web服务。示例代码:
1 2 3 4 5 6 7 8 9 10 11 from werkzeug.serving import run_simplefrom werkzeug.wrappers import Responsedef func (environ, start_response) : response = Response('Hello World!' ) return response(environ, start_response) if __name__ == '__main__' : run_simple(hostname='127.0.0.1' , port=9000 , application=func)
运行结果:
1 2 3 WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on http://127.0.0.1:9000 Press CTRL+C to quit
然后,我们访问http://127.0.0.1:9000
,能看到Hello World!
。
如果我们把上述代码改一下,自定义一个Flask类,run_simple
的application
填Flask类的对象,那么最终会调用__call__
方法。示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from werkzeug.serving import run_simplefrom werkzeug.wrappers import Responseclass Flask (object) : def __call__ (self, environ, start_response) : response = Response('Hello World!' ) return response(environ, start_response) app = Flask() if __name__ == '__main__' : run_simple(hostname='127.0.0.1' , port=9000 , application=app)
运行结果:
1 2 3 WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on http://127.0.0.1:9000 Press CTRL+C to quit
如果我们再改一下,讲run_sample
放在Flask类中,通过成员方法run
调用run_sample
。示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from werkzeug.serving import run_simplefrom werkzeug.wrappers import Responseclass Flask (object) : def __call__ (self, environ, start_response) : response = Response('Hello World!' ) return response(environ, start_response) def run (self) : run_simple(hostname='127.0.0.1' , port=9000 , application=self) app = Flask() if __name__ == '__main__' : app.run()
运行结果:
1 2 3 WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on http://127.0.0.1:9000 Press CTRL+C to quit
到这一步,就非常像Flask了。
特别的,我们能在flask的app.py
中,找到__call__
,示例代码:
1 2 3 4 5 6 def __call__ (self, environ: dict, start_response: t.Callable) -> t.Any: """The WSGI server calls the Flask application object as the WSGI application. This calls :meth:`wsgi_app`, which can be wrapped to apply middleware. """ return self.wsgi_app(environ, start_response)
self.wsgi_app(environ, start_response)
,再往内部,就是Flask的内部源码了,我们会在后续的文章中进行更详细的讨论。
响应
返回文本
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 from flask import Flaskapp = Flask(__name__) @app.route('/res') def res () : return 'res' if __name__ == '__main__' : app.run()
返回JSON
返回JSON,需要依赖jsonify
,在flask
包中。
1 from flask import jsonify
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 from flask import Flask, jsonifyapp = Flask(__name__) @app.route('/res') def res () : res_l = [{'a' : 1 }, {'b' : 2 }] return jsonify(res_l) if __name__ == '__main__' : app.run()
template
返回模板页面,需要依赖render_template
,在flask
包中。
1 from flask import render_template
模版默认位于templates
目录下,可以在app = Flask()
中根据template_folder
参数进行配置。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 from flask import Flask, render_templateapp = Flask(__name__) @app.route('/login') def login () : return render_template('login.html' ) if __name__ == '__main__' : app.run()
关于模板,内容很多,包括如何给模版传参等。但是鉴于在实际开发中,几乎不用,我们不讨论。
接收请求的方式
现象
假设存在一个模板如下,其中表单的方法是post
。
1 2 3 4 5 6 7 8 9 10 11 12 13 <!DOCTYPE html > <html lang ="en" > <head > </head > <body > <h1 > 用户登录</h1 > <form method ="post" > <input type ="text" name ="uid" /> <input type ="text" name ="pwd" /> <input type ="submit" name ="提交" > </form > </body > </html >
点击登录后,会返回如下,Method Not Allowed
。
解决
这是因为@app.route()
默认支持的方式是GET
,如果需要支持其他方式,需要我们指定methods
。
1 @app.route('/login', methods=['GET', 'POST'])
获取参数
request对象
在本文开头,我们说"在请求处理方面,Django的请求处理是逐一封装和传递,Flask的请求处理是利用上下文管理来实现的"。
在这里,就会看到具体的体现。
获取参数不依赖login方法的形参(传递给login方法的参数),而是依赖request
对象,直接从request
对象中获取。
1 from flask import request
获取GET请求参数
在采用GET请求方式传递参数时,参数直接拼接在URL中,可以如下两种方式获取:
request.args.get('key')
request.values.get('key')
假设存在一个请求如下:
1 curl --location 'http://127.0.0.1:5000/req?key=qwert'
获取参数,示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 from flask import Flask, render_template, requestapp = Flask(__name__) @app.route('/req', methods=['GET', 'POST']) def req () : print(request.args.get('key' )) print(request.values.get('key' )) if __name__ == '__main__' : app.run()
运行结果:
获取JSON参数
获取JSON参数有两种方法,两种方法获取到的都是字典格式,不需要再进行json.loads()
request.get_json()
request.json
假设存在一个请求如下:
1 2 3 4 5 6 curl --location 'http://127.0.0.1:5000/login' \ --header 'Content-Type: application/json' \ --data '{ "k1":"v1", "k2":"v2" }'
获取参数,示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from flask import Flask, requestapp = Flask(__name__) @app.route('/req', methods=['GET', 'POST']) def req () : print(request.get_json()) print(type(request.get_json())) print(request.json) print(type(request.json)) if __name__ == '__main__' : app.run()
运行结果:
1 2 {'k1': 'v1', 'k2': 'v2'} {'k1': 'v1', 'k2': 'v2'}
获取表单数据
获取表单数据,基于request.form
。
获取某个具体值的方法为:
request.form.get('k1')
request.form['k1']
1 2 3 4 curl --location 'http://127.0.0.1:5000/req' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'k1=v1' \ --data-urlencode 'k2=v2'
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from flask import Flask, render_template, requestapp = Flask(__name__) @app.route('/req', methods=['GET', 'POST']) def req () : print(request.form) print(request.form.get('k1' )) print(request.form['k1' ]) if __name__ == '__main__' : app.run()
运行结果:
1 2 3 ImmutableMultiDict([('k1', 'v1'), ('k2', 'v2')]) v1 v1
解决问题
回到上文的现象,我们接受表单的POST请求。示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from flask import Flask, render_template, requestapp = Flask(__name__) @app.route('/login', methods=['GET', 'POST']) def login () : if request.method == 'GET' : return render_template('login.html' ) uid = request.form.get('uid' ) pwd = request.form.get('pwd' ) if uid == 'kaka' and pwd == 'pwd' : return '登录成功' else : return '登录失败' if __name__ == '__main__' : app.run()
解释说明:request.method
,获取调用方法
RESTful
关于什么是RESTful
,可以参考《基于Java的后端开发入门:17.SpringMVC》 。
这里主要讨论在Flask中,如何实现RESTful
。
假设存在一个请求,如下:
1 curl --location 'http://127.0.0.1:5000/delete/123'
获取参数,示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 from flask import Flaskapp = Flask(__name__) @app.route('/delete/<idv>', methods=['GET', 'POST']) def delete (idv) : print(idv) print(type(idv)) if __name__ == '__main__' : app.run()
运行结果:
默认数据类型是str
。如果我们想指定参数的数据类型,例如指定为int
,可以在前面填上int:
。示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 from flask import Flaskapp = Flask(__name__) @app.route('/delete/<int:idv>', methods=['GET', 'POST']) def delete (idv) : print(idv) print(type(idv)) if __name__ == '__main__' : app.run()
运行结果:
redirect、endpoint和url_for
现在,我们想登录成功后,跳转到新的页面。
基于redirect
:
1 from flask import redirect
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 from flask import Flask, render_template, request, redirectapp = Flask(__name__) @app.route('/login', methods=['GET', 'POST']) def login () : if request.method == 'GET' : return render_template('login.html' ) uid = request.form.get('uid' ) pwd = request.form.get('pwd' ) if uid == 'kaka' and pwd == 'pwd' : return redirect('/index' ) else : return '登录失败' @app.route('/index', endpoint='idx') def index () : return 'ok' if __name__ == '__main__' : app.run()
此外,我们还可以在@app.route()
中,指定endpoint
参数的值,并用url_for
方法解析。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 from flask import Flask, render_template, request, redirect, url_forapp = Flask(__name__) @app.route('/login', methods=['GET', 'POST']) def login () : if request.method == 'GET' : return render_template('login.html' ) uid = request.form.get('uid' ) pwd = request.form.get('pwd' ) if uid == 'kaka' and pwd == 'pwd' : return redirect(url_for('idx' )) else : return '登录失败' @app.route('/index', endpoint='idx') def index () : return 'ok' if __name__ == '__main__' : app.run()
注意,endpoint有两个特点:
不可重复
默认为函数名
(所以,有时候我们发现报错,endpoint重复,可能是我们指定的名称和另一个函数的名称重复了。)
Session
如何使用Session
关于什么是Session,可以参考《基于Java的后端开发入门:13.Servlet、Filter和Listener》 的"Session"部分。
这里主要讨论在Flask中,如何实现会话(Session)。
一共三步:
from flask import session
。
app.secret_key = '123456'
,指定Session的key。
在需要设置Session的地方,进行设置。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 from flask import Flask, render_template, request, redirect, url_for, sessionapp = Flask(__name__) app.secret_key = '123456' @app.route('/login', methods=['GET', 'POST']) def login () : if request.method == 'GET' : return render_template('login.html' ) uid = request.form.get('uid' ) pwd = request.form.get('pwd' ) if uid == 'kaka' and pwd == 'pwd' : session['uid' ] = uid return redirect(url_for('idx' )) else : return '登录失败' @app.route('/index', endpoint='idx') def index () : return 'ok' if __name__ == '__main__' : app.run()
如果我们没有指定app.secret_key
,会有如下的报错:
1 2 3 RuntimeError: The session is unavailable because no secret key was set. Set the secret_key on the application to something unique and secret. 127.0.0.1 - - [25/Oct/2023 20:54:26] "POST /login HTTP/1.1" 500 -
装饰器
上述存在一个问题,我们需要在每一个需要校验session的方法前,都加上
1 2 if not session.get('uid' ): return 'error'
在《基于Python的后端开发入门:3.拷贝、类型注解、闭包、装饰器和一些常用的包》 ,我们讨论过装饰器,可以自定义一个装饰器。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 from flask import Flask, render_template, request, redirect, url_for, sessionimport functoolsapp = Flask(__name__) app.secret_key = 'ajldjajdajdjasldjl' def auth (func) : @functools.wraps(func) def inner () : if not session.get('uid' ): return 'error' return func() return inner @app.route('/login', methods=['GET', 'POST']) def login () : if request.method == 'GET' : return render_template('login.html' ) uid = request.form.get('uid' ) pwd = request.form.get('pwd' ) if uid == 'kaka' and pwd == 'pwd' : session['uid' ] = uid return redirect(url_for('idx' )) else : return '登录失败' @app.route('/index', endpoint='idx') @auth def index () : return 'ok' if __name__ == '__main__' : app.run()
解释说明:
@app.route('/index')
,会读取函数,并将函数名作为endpoint的名字。
如果装饰器没加functools
,就可能会导致endpoint重复。
如果加了我们自定义的装饰器在@app.route
的前面,同样可能会导致endpoint重复。
蓝图
蓝图,构建业务功能可拆分的目录结构。
如下,是一个蓝图目录结构:
v1.py
,第一个蓝图,示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 from flask import Blueprintv1_app = Blueprint('v1' , __name__) @v1_app.route('/f1') def f1 () : return 'f1' @v1_app.route('/f2') def f2 () : return 'f2'
v2.py
,第二个蓝图,示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 from flask import Blueprintv2_app = Blueprint('v2' , __name__) @v2_app.route('/f1') def f1 () : return 'f1' @v2_app.route('/f2') def f2 () : return 'f2'
__init__.py
,构造一个Flask的对象,并把上文的两个蓝图添加进去。示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from flask import Flaskdef create_app () : app = Flask(__name__) app.secret_key = '123456' @app.route('/index') def index () : return 'index' from .views import v1 app.register_blueprint(v1.v1_app, url_prefix='/admin' ) from .views import v2 app.register_blueprint(v2.v2_app, url_prefix='/web' ) return app
manage.py
,程序的主入口,示例代码:
1 2 3 4 5 6 from flask_demo import create_appapp = create_app() if __name__ == '__main__' : app.run()