flask简述
Flask 是 Python 生态圈中一个基于 Python 的Web 框架。其轻量、模块化和易于扩展的特点导致其被广泛使用,适合快速开发 Web 应用以及构建小型到中型项目。它提供了开发 Web 应用最基础的工具和组件。之所以称为微框架,是因为它与一些大型 Web 框架(如 Django)不同,并不捆绑数据库管理、表单验证等功能,而是保持了极简的核心,只包含了路由、模板引擎和WSGI服务器的基本功能,使开发者可以根据需求选择合适的扩展来构建应用。掌握 Flask 的基础之后可以深入学习如何实现更复杂的功能,例如用户认证、API 版本管理、性能优化,以及如何与前端框架(如 Vue.js 或 React)进行集成,以构建现代的全栈应用。Flask 的设计哲学是简单优先、灵活性强,让开发者对应用的构建过程有更多的控制。Flask 由 Armin Ronacher 开发,基于两个核心工具库:Werkzeug和Jinja2。
Werkzeug是一个 WSGI工具包,用于处理 HTTP 请求和响应。
Jinja2是一个灵活的模板引擎,用于生成动态 HTML 页面。
Flask 的架构是典型的 MVC(Model-View-Controller)风格中的一种实现。这种架构可以有效分离应用的业务逻辑、数据管理和视图展示,有助于保持代码的清晰性和模块化,核心功能主要包括:
基本要点
1. 路由系统
Flask 使用 @app.route() 装饰器定义 URL 路由,这使得定义一个请求的处理函数变得非常简洁。每个 URL 路径都对应一个视图函数,它负责处理来自客户端的请求并返回合适的响应。类似于之前提到过的fastapi:
from flask import Flask
app = Flask(__name__)
# 路由与视图函数一一对应,程序实例需要知道每个url请求所对应的运行代码是谁,所以程序中必须要创建一个url请求地址到python运行函数的一个映射
@app.route("/hello") # 将路径 /hello 映射到函数 hello_world,当用户访问这个路径时,浏览器会得到 Hello, World! 的响应,url映射的函数,要传参则在上述route(路由)中添加参数申明
def hello_world():
return "Hello, World!"
# 注意:如果ip设置成0.0.0.0(不仅监听本地端口)且端口对外开发,那么在任意主机上都可以访问该地址,请确保你本地数据处于安全状态不受攻击
if __name__ == "__main__":
app.run(host="127.0.0.1", port=8080, debug=False)
访问上述路径是会打印出该函数结果:
2. 请求和响应处理
Flask 对 HTTP 请求和响应的处理非常灵活。通过 flask.request 对象,可以访问请求的所有细节,如查询参数、表单数据、上传的文件等。对于响应,开发者可以使用 flask.Response 类来自定义 HTTP 响应。
from flask import Flask, request
app = Flask(__name__)
#注意路由路径不要重名,映射的视图函数也不要重名,methods表示请求方式
@app.route("/greet", methods=["GET", "POST"])
def greet():
if request.method == "POST":
name = request.form.get("name")
return f"Hello, {name}!"
return """
<form method="POST">
Name: <input type="text" name="name">
<input type="submit" value="Submit">
</form>
"""
if __name__ == "__main__":
app.run(debug=False)
访问上述路径时如下图:
3、路由
可以在路径内以/<参数名>的形式指定参数,默认接收到的参数类型是string
以下为flask框架自带的转换器,可以置于参数前将接收的参数转化为对应类型
string 接受任何不包含斜杠的文本
int 接受正整数
float 接受正浮点数
path 接受包含斜杠的文本
当然也可以自定义转换器(pip install werkzeug
):
from werkzeug.routing import BaseConverter #导入转换器的基类,用于继承方法
from flask import Flask
app = Flask(__name__)
# 自定义转换器类
class RegexConverter(BaseConverter):
def __init__(self,url_map,regex):
# 重写父类定义方法
super(RegexConverter,self).__init__(url_map)
self.regex = regex
def to_python(self, value):
# 重写父类方法,后续功能已经实现好了
print('to_python方法被调用')
return value
# 将自定义的转换器类添加到flask应用中
# 具体过程是添加到Flask类下url_map属性(一个Map类的实例)包含的转换器字典属性中
app.url_map.converters['re'] = RegexConverter
# 此处re后括号内的匹配语句,被自动传给我们定义的转换器中的regex属性
# value值会与该语句匹配,匹配成功则传达给url映射的视图函数
@app.route("/index/<re('1d{10}'):value>")
def index(value):
print(value)
return "Hello World!"
if __name__=='__main__':
app.run(debug=False)
4、endpoint
每个实例app中都存在一个url_map,这个url_map中包含了url到endpoint的映射;当request请求传来一个url的时候,会在url_map中先通过rule找到endpoint,然后再在view_functions中根据endpoint再找到对应的视图函数view_func
from flask import Flask
app = Flask(__name__)
# endpoint默认为视图函数的名称
@app.route('/branch')
def test():
return 'check success!'
# 可以在路由中修改endpoint,相当于为视图函数起别名(当视图函数名称很长时适用)
@app.route('/hello',endpoint='hello_test')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
print(app.view_functions)
print(app.url_map)
app.run()
- 可以通过
view_functions
查看到当前endpoint与视图函数的对应情况; - 可以通过
url_map
查看当前url与endpoint的绑定情况;
endpoint默认为视图函数的名称,endpoint相当于给url起一个名字,view_functions内存储的就是url的名字到视图函数的映射,且endpoint在同一个蓝图下也不能重名,要注意即使修改endpoint为其他视图函数名,路由依然是绑定其正下方的第一个视图函数,说明endpoint作用于url,而不是作用于函数名。
5、redirect重定向
在flask 中,重定向是通过flask.redirect(location, code=302)这个函数来实现的,location表示需要重定向的url, 应该配合url_for函数来使用, code表示采用哪个重定向,默认是302,即临时性重定向, 可以修改为301来实现永性重定向;
from flask import Flask,jsonify
app = Flask(__name__)
# endpoint默认为视图函数的名称
#用jsonify库实现将json数据返回给前端
@app.route('/branch')
def test():
data={'suatus':'check success!'}
return jsonify(data)
# 可以在路由中修改endpoint,相当于为视图函数起别名(当视图函数名称很长时适用)
@app.route('/hello',endpoint='hello_test')
def hello_world():
#doing something
#...
# redirect重定位(服务器向外部发起一个请求跳转)到一个url界面;
# url_for给指定的函数构造 URL;
# return redirect('/items') 不建议这样做,将界面限死了
return redirect(url_for('items'))
if __name__ == '__main__':
print(app.view_functions)
print(app.url_map)
app.run()
6、abort函数
类似于python中的raise函数,可以在需要退出请求的地方抛出错误,并结束该请求;
可以使用errorhandler()装饰器来进行异常的捕获与自定义
from flask import Flask,jsonify
app = Flask(name)
endpoint默认为视图函数的名称
#用jsonify库实现将json数据返回给前端
@app.route(‘/branch’)
def test():
data={‘suatus’:‘check success!’}
return jsonify(data)可以在路由中修改endpoint,相当于为视图函数起别名(当视图函数名称很长时适用)
@app.route(‘/hello’,endpoint=‘hello_test’,methods=[‘GET’,‘POST’])
def hello_world():if request.method == 'GET': #doing something pass else: # abort的用法类似于python中的raise,在网页中主动抛出错误 abort(404) # redirect重定位(服务器向外部发起一个请求跳转)到一个url界面; # url_for给指定的函数构造 URL; # return redirect('/items') 不建议这样做,将界面限死了 return redirect(url_for('items'))
自定义错误处理方法,将404这个error与Python函数绑定
当需要抛出404error时,将会访问下面的代码
@app.errorhandler(404)
def handle_404_error(err):
# return “发生了错误,错误情况:%s”%err
# 自定义一个界面
return render_template(‘404.html’)if name == ‘main’:
print(app.view_functions)
print(app.url_map)
app.run()
自定义的404界面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- 注意图片文件需要放在一个静态文件夹static里 -->
<img src="../static/error404.jpg" alt="" width="1428px" height="57px">
</body>
</html>
二、Flask 的扩展与灵活性
虽然 Flask 是一个微框架,但它具有极强的灵活性,可以自由选择各种扩展来增强功能:
Flask-SQLAlchemy:用于与关系数据库交互,提供 ORM(对象关系映射)支持。是一个流行的 Flask 扩展,它为数据库操作提供了一种更简洁、更 Pythonic 的方式。
Flask-WTF:用于表单处理和验证,简化表单开发。
Flask-Login:用于用户认证与会话管理。
这些扩展可以无缝集成到 Flask 应用中,使开发者在不牺牲灵活性的同时实现复杂的功能。
1、Flask-SQLAlchemy与数据库sqllite交互
#app.py
from flask import Flask
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///127.0.0.1.db" # 相对路径
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False # 禁用模型修改跟踪,节省资源
db = SQLAlchemy(app)
migrate = Migrate(app, db) # 数据库表结构更新迁移
class bank_info(db.Model):
id = db.Column(db.Integer, primary_key=True)
branch_id = db.Column(db.String(80), unique=True, nullable=False)
application = db.Column(db.String(120), unique=True, nullable=False)
def __repr__(self):
return f"<bank_info branch_id={self.branch_id}, application={self.application}>"
@app.route("/add_branch_id")
def add_branch_id():
# 添加一个新的分支机构
branch = bank_info(branch_id="****", application="授信总额:****")
# 检查是否已经存在该 branch_id 或 application
existing_branch = bank_info.query.filter_by(branch_id=branch.branch_id).first()
if existing_branch:
return "Branch ID already exists!"
db.session.add(branch)
db.session.commit()
return "Branch added!"
@app.route("/check_branch_id")
def get_branch():
# 查询所有信息
branches = bank_info.query.all()
return "<br>".join([f"ID: {branch.id}, branch_id: {branch.branch_id}, application: {branch.application}" for branch in branches])
if __name__ == "__main__":
with app.app_context():
db.create_all()# 在应用上下文中创建数据库表
app.run(debug=False) # 启动
运行后输入路由结果为:
查询结果同理。
注意当表结构需要变更时,需要单独执行命令:
cd ./path/ # 项目目录下,即app.py所在目录
flask db init # 初始化迁移环境,创建一个 migrations/ 文件夹
flask db migrate -m "Add email column to bank_info model" # -m 用于写一个描述迁移的消息
flask db upgrade # 执行迁移
# 如果删除了原来数据库文件需要重建
from your_app import db
db.create_all()
#直接查看表结构
sqlite3 your_database.db
.schema bank_info
同时需要更新脚本:
#app.py
from flask import Flask
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///127.0.0.1.db" # 使用相对路径
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False # 禁用模型修改跟踪,以节省资源
db = SQLAlchemy(app)
migrate = Migrate(app, db)#执行数据库迁移
class bank_info(db.Model):
id = db.Column(db.Integer, primary_key=True)
branch_id = db.Column(db.String(80), unique=False, nullable=False)
application = db.Column(db.String(120), unique=False, nullable=False)
email = db.Column(db.String(120), nullable=True) # 添加email字段
def __repr__(self):
return f"<bank_info branch_id={self.branch_id}, application={self.application}, email={self.email}>"
@app.route("/add_branch_id")
def add_branch_id():
# 添加一个新的分支机构
branch = bank_info(branch_id="**1", application="授信总额:*",email="***@163.com")
# 检查是否已经存在该 branch_id 或 application
existing_branch = bank_info.query.filter_by(branch_id=branch.branch_id).first()
if existing_branch:
return "Branch ID already exists!"
db.session.add(branch)
db.session.commit() # 提交到数据库
return "Branch added!"
@app.route("/check_branch_id")
def get_branch():
# 查询所有信息
branches = bank_info.query.all()
return "<br>".join([f"ID: {branch.id}, branch_id: {branch.branch_id}, application: {branch.application},email:{branch.email}" for branch in branches])
if __name__ == "__main__":
# with app.app_context():
# db.create_all()# 在应用上下文中创建数据库表
app.run(debug=False) # 启动
注意,当字段设置unique为True时,每次插入的数据必须要求不一致,否则无法插入。在项目目录下会生成对应的文件:
2、Jinja2 模板引擎的使用
Jinja2 是 Flask 的默认模板引擎,允许开发者将动态数据嵌入到 HTML 中,生成富有交互性的页面。它支持变量、控制结构(如 for 循环和 if 判断)以及宏(类似于函数的代码片段,可以复用):
#模板文件存在于 templates/hello.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Greeting</title>
</head>
<body>
<h1>Hello, {
{ name }}!</h1>
</body>
</html>
- render_template():可以用于呈现一个开发人员编写的html文件模板
- request.method用于获取url接收到的请求方式,以此返回不同的响应页面
在 Flask 应用中使用这个模板:
from flask import Flask, render_template
# 创建 Flask 应用实例
app = Flask(__name__)
# 路由处理函数
@app.route("/greet/<name>",methods=['GET','POST'])#url映射的函数,要传参则在上述route(路由)中添加参数申明
def greet(name):
if request.method == 'GET':
# 想要html文件被该函数访问到,首先要创建一个templates文件,将html文件放入其中
# 该文件夹需要被标记为模板文件夹,且模板语言设置为jinja2
return render_template("hello.html", name=name)
# 此处欲发送post请求,需要在对应html文件的form表单中设置method为post
elif request.method == 'POST':
pass
# 启动 Flask 应用
if __name__ == '__main__':
app.run(debug=False)
render_template() 函数用于渲染 hello.html 模板,并将变量 name 的值传递到模板中,从而动态生成最终的 HTML 页面.
模板的第一个参数为指定的模板文件名称,如自定义的html文件,第二个(及后续)参数为可选项,用于向模板中传递变量。
from flask import Flask,render_template
app = Flask(name)
给前端模板传参
@app.route(“/”)
def index():
data = {
‘name’:‘张三’,
‘age’:18,
‘mylist’:[1,2,3,4,5,6,7]
}
# 以键值对的形式传参给模板index2.html
# 左边是要在前端调用时使用的变量名称(形参:data);
# 右边是给这个变量传的值(实参:字典data);
return render_template(‘index2.html’,data=data)if name == ‘main’:
app.run()如果有多个变量需要传递,我们可以不需要一个一个进行传参,直接使用
**locals()
替代我们在当前视图函数中定义的所有变量:from flask import Flask,render_template
app = Flask(name)
给前端模板传参
@app.route(“/”)
def index():
title=‘python键值对’ # 定义键值1
author=‘li’ # 定义键值2
return render_template(‘index2.html’,**locals()) #渲染模型并传值if name == ‘main’:
app.run()在前端直接使用定义时的变量名就可以使用该变量,即
{ { title }}
和{ { author }}
。
模板中的控制语句:jinja2模板引擎中也可使用if和for控制语句,但是语句需要放置在{% %}中;
if条件判断语句必须包含结束标签{% endif %},其他部分与python中类似,可以与比较运算符> >= < <= == !=结合使用,或与逻辑运算符and,or,not,()结合使用;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% if name==1 %}
<h1>恭喜你抽中了一等奖!</h1>
{% if name==2 %}
<h1>恭喜你抽中了二等奖!</h1>
{% else %}
<h1>恭喜你抽中了三等奖!</h1>
{% endif %}
</body>
</html>
for循环控制语句在模板内的用法也和python中类似,遍历的对象可以是字典、元组、列表等,但需要注意的是在模板中无法使用continue和break来对循环进行控制;
常见常量:
loop.index: 获取当前的索引值 从1开始
loop.index0:获取当前的索引值 从0开始
loop.first: 判断当前是否是第一次迭代, 是返回True否则返回False
loop.last: 判断当前是否是最后一次迭代, 是返回True否则返回False
loop.length: 序列的长度{% for 目标 in 对象 %}
目标
{% endfor %}- {% for item in list %}
- {
{ item }}
- 当前的索引是:{
{ loop.index }}
- 当前的索引是:{
{ loop.index0 }}
- 当前是否是第一次迭代:{
{ loop.first }}
- 当前是否是最后一次迭代:{
{ loop.last }}
- 前序列的长度:{
{ loop.length }}
宏的定义、调用与导入
宏的定义是为了将前端模板中需要反复创建的模块变成一个方便调用的“函数”,这一操作类似于python中创建函数,也可以传参,但不能有返回值;
宏的定义以macro标志开始,以endmacro结束,同样需要在
{% %}
中进行。{% macro input1() %}
{% endmacro %}{% macro input2(name, value=‘’, type=‘text’, size=30) %}
{ type }}" name="{
{ name }}" value="{
{ value }}" size="{
{ size }}">
{% endmacro %}宏的调用同样类似于函数的调用,如果未采用关键字传参则要注意顺序。
{
{ input1() }}
{
{ input2() }}
{
{ input2(‘username’) }}
{
{ input2(‘username’, value=‘cheng’, type=‘password’, size=50) }}
宏的导入可以使用语句import 模板文件名 as 别名或from 模板文件名 import 宏名称,就像python中库和包的导入一样;
将宏单独定义在一个html模板文件中后,就可以通过导入这个模板文件来调用里面的所有宏,导入过程同样在{% %}中进行,调用过程在{ { }}在进行。
<!-- 上述宏定义在了一个index3.html的文件中 -->
{% import 'index3.html' as index3 %}
<div>
<!-- 调用导入的宏模板文件中的宏,实现登录页面构建 -->
<p>用户名:{
{index3.input2('username')}}</p>
<p>密码:{
{index3.input2('password',type='password')}}</p>
<p>登录:{
{index3.input2('submit',type='submit',value='登录')}}</p>
</div>
<!-- 另一种导入方式 -->
{% from 'index3.html' import input2 %}
<div>
<!-- 此时直接调用input2即可 -->
<p>用户名:{
{input2('username')}}</p>
<p>密码:{
{input2('password',type='password')}}</p>
<p>登录:{
{input2('submit',type='submit',value='登录')}}</p>
</div>
3、构建完整的 CRUD 应用
一个典型的 Web 应用需要对数据进行创建(Create)、读取(Read)、更新(Update)和删除(Delete),这被称为 CRUD 操作。借助 Flask可以很方便地构建一个支持 CRUD 操作的应用:
#用 Flask 构建一个完整的 CRUD API。通过 HTTP 的 POST、GET、PUT 和 DELETE 方法,客户端可以实现对 Item 对象的创建、读取、更新和删除操作
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///crud.db"
db = SQLAlchemy(app)
class Item(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True, nullable=False)
def to_dict(self):
return {"id": self.id, "name": self.name}
@app.route("/items", methods=["POST"])
def create_item():
name = request.json.get("name")
item = Item(name=name)
db.session.add(item)
db.session.commit()
return jsonify(item.to_dict()), 201
@app.route("/items", methods=["GET"])
def get_items():
items = Item.query.all()
return jsonify([item.to_dict() for item in items])
@app.route("/items/<int:item_id>", methods=["PUT"])
def update_item(item_id):
item = Item.query.get_or_404(item_id)
item.name = request.json.get("name")
db.session.commit()
return jsonify(item.to_dict())
@app.route("/items/<int:item_id>", methods=["DELETE"])
def delete_item(item_id):
item = Item.query.get_or_404(item_id)
db.session.delete(item)
db.session.commit()
return "", 204
if __name__ == "__main__":
with app.app_context():
db.create_all()
app.run(debug=False)
运行上行脚本,打开postman新建一个文件执行操作:
点击send如果成功则会出现:
在浏览器里面输入对应路由显示为:
表明数据新建成功。
如果想要更新或者是删除操作则分别选择put和DELETE选项,发送给对应请求,请求体路由输入对应ID即可,请求体输入对应新的名称:
4、Flask 中间件与蓝图
4.1. 中间件
中间件(Middleware)是位于请求与响应之间的代码,用于对请求或响应进行处理。Flask 的中间件可以用来做很多事情,例如:记录日志、修改请求、或在响应中增加自定义的 HTTP 头等:
from flask import Flask, jsonify
app = Flask(__name__)
# 定义在请求处理之前执行的函数
@app.before_request
def before_request_func():
print("Before request is called")
# 定义在响应返回之前执行的函数
@app.after_request
def after_request_func(response):
print("After request is called")
# 在这里你可以修改响应(如添加头信息等)
response.headers["X-Custom-Header"] = "Hello World"
return response
# 一个简单的路由
@app.route('/')
def home():
return jsonify(message="Hello, Flask!")
if __name__ == '__main__':
app.run(debug=True)
@app.before_request
装饰器用于定义在每个请求处理之前执行的函数。这通常用于一些预处理任务,比如验证用户身份、记录日志、设置请求上下文等。
- 该函数表示每一次请求之前,可以执行某个特定功能的函数;
- 可以存在多个before_request装饰器,执行顺序是由上到下,先绑定的先执行;
- 并且先执行 flask app 的 before_request, 再执行 blueprint 的 before_request;
- 执行时机:当客户端发出请求时,Flask 在请求被处理之前调用
before_request_func
。 - 常见用途:检查用户是否已经登录,处理跨域请求(CORS),或者设置数据库连接等。
@app.after_request
装饰器用于定义在每个请求处理完成后、响应返回给客户端之前执行的函数。这个函数接受一个 response
参数,可以对响应进行修改或者执行一些收尾操作。
执行时机:当请求处理完成并且响应准备返回时,Flask 会调用
after_request_func
。你可以对response
进行修改(例如添加 headers、修改响应内容等)。常见用途:设置响应头、日志记录、性能监控、跨域处理等。
@app.before_request
和@app.after_request
是应用级别的装饰器,适用于每一个请求。@app.after_request
装饰器中的函数必须返回一个响应对象。如果没有对response
做任何修改,至少需要返回原始的response
对象。
@app.before_first_request
:与before_request的区别是,只在第一次请求之前调用;
- 该函数表示第一次请求之前可以执行的函数;
- 执行顺序同样也是先绑定的先执行;
@app.teardown_request:每一次请求之后都会调用;
- 该函数接收一个参数,该参数是服务器出现的错误信息;
- 执行顺序也是先绑定的后执行;
- 只有在请求上下文被 pop 出请求栈的时候才会直接跳转到teardown_request;
- 所以在被正常调用之前,即使某次请求有抛出错误,该请求也都会被继续执行, 并在执行完后返回 response;
4.2. 蓝图(Blueprint)
当应用规模变大时,代码结构的组织变得至关重要。Flask 提供了 蓝图(Blueprint)组件来帮助开发者将应用模块化。在 Flask 中,应用可以通过蓝图将不同的视图函数和路由分离开来,使得代码更加结构化和易于管理。蓝图的作用是将视图函数、错误处理、静态文件、模板等逻辑与主应用程序分开,便于进行模块化开发。在一个大型应用程序中,蓝图可以将不同的功能区域分离,使得不同的功能部分有独立的管理。在 Flask 应用中,可以通过app.register_blueprint()
将蓝图注册到应用程序中,之后蓝图中的路由和视图就会成为主应用的一部分。
将用户相关的路由逻辑组织在一个独立的蓝图中,并在主应用中注册这个蓝图,从而使代码结构更加清晰和模块化:,一个实例下的蓝图对象不可重名。
# 创建蓝图:user.py
from flask import Blueprint
# 创建一个名为 "user" 的蓝图模块
user_bp = Blueprint("user", __name__)
# 定义蓝图中的一个路由
@user_bp.route("/user/<username>")
def show_user(username):
return f"User: {username}"
# 注册蓝图
from flask import Flask
from user import user_bp
# from article import article_bp
app = Flask(__name__)
# 注册多个蓝图
app.register_blueprint(user_bp)
# app.register_blueprint(article_bp)
if __name__ == "__main__":
app.run(debug=True)
blueprint对象的工作方式与Flask对象类似,但其不是一个单独的应用;
每拓展一个蓝图对象,就要在主路由文件下添加一行注册代码;
蓝图对象内记录了当前模块下的所有视图函数,故视图函数不可与蓝图对象同名;
在蓝图内需要通过蓝图对象来定义路由和调用其他装饰器,蓝图对象定义的路由处于休眠状态,在蓝图被注册时才成为程序的一部分。
url_prefix设置蓝图前缀
一般在蓝图对象定义时添加,为当前蓝图下的所有视图函数添加统一的前缀,这样不同蓝图下的视图函数的url就不易发生重复;
new_list = Blueprint('test',__name__,url_prefix='/index')
3 添加前缀后,加载该路由的url就变为"/index/test":
@new_list.route('/test')
def test():
return 'hello dog!'
5、url与视图函数的绑定
实现url与视图函数的绑定,除了使用路由装饰器@app.route,还可以通过add_url_rule(rule,endpoint=None,view_func=None)方法,其中:
rule:设置的url
endpoint:给url设置的名称
view_func:指定视图函数的名称
from flask import Flask,url_for
app = Flask(__name__)
![qwqw](https://i-blog.csdnimg.cn/direct/28c3f979e67f4abab03bc2ab850b2837.png#pic_center)
@app.route('/branch',endpoint='branch')
# 底层其实是用add_url_rule实现的
def check_branch():
return 'branch Hive is null'
def my_test():
return '这是测试查询页面'
app.add_url_rule(rule='/test',endpoint='test',view_func=my_test)
# 请求上下文只有在发送request请求时才会被激活,激活后request对象被设置为全局可访问
# 其内部封装了客户端发出的请求数据报文
# 此处是主动生成一个临时的测试请求上下文
with app.test_request_context():
print(url_for('test')) # 输出结果为/test
if __name__ == '__main__':
app.run(debug=False)
6、类试图和自定义装饰器
视图还可以由类来实现,即标准类视图:
定义时需要继承flask的views.View这一基类;
每个类视图内必须包含一个dispatch_request方法,每当类视图接收到请求时都会执行该方法,返回值的设定和视图函数相同;
视图函数可以通过@app.route和app.add_url_rule来进行注册(映射到url),但类视图只能通过app.add_url_rule来注册,注册时view_func不能直接使用类名,需要调用基类中的as_view方法来为自己取一个“视图函数名”
采用类视图的最大优势,就是可以把多个视图内相同的东西放在父类中,然后子类去继承父类;而类视图不方便的地方,就是每一个子类都要通过一个add_url_rule来进行注册。
创建一个网站包含三个页面,每个页面中都展示相同的广告:
from flask import Flask,render_template,views
app = Flask(__name__)
# 定义父视图类继承基类View
class Ads(views.View):
def __init__(self):
super(Ads, self).__init__()
# 实例属性
self.context={
'ads':'这是一则广告!'
}
# 定义子视图类继承父类并实现工程
class Index(Ads):
def dispatch_request(self):
# 字典传参方式==不定长的关键字传参
return render_template('class_mould/index.html',**self.context)
class Login(Ads):
def dispatch_request(self):
# 字典传参方式==不定长的关键字传参
return render_template('class_mould/login.html',**self.context)
class Register(Ads):
def dispatch_request(self):
# 字典传参方式==不定长的关键字传参
return render_template('class_mould/register.html',**self.context)
# 注册类视图,as_view给类视图起名
app.add_url_rule(rule='/',endpoint='index',view_func=Index.as_view('index'))
app.add_url_rule(rule='/login/',endpoint='login',view_func=Login.as_view('login'))
app.add_url_rule(rule='/register/',endpoint='register',view_func=Register.as_view('register'))
if __name__=='__main__':
print(app.view_functions)
app.run(debug=False)
首页index.html:
Title 这是首页!{{ ads }}
登录页面login.html:
Title 这是登录页面!{{ ads }}
注册页面register.html:
Title 这是注册页面!{{ ads }}
可以通过调用app.view_functions来查看当前的endpoint绑定情况,发现已经变为as_view转化后的类视图:
{‘static’: <function Flask.init.. at 0x0000024163C46D30>, ‘index’: <function View.as_view..view at 0x0000024164B58CA0>, ‘login’: <function View.as_view..view at 0x0000024164BCB280>, ‘register’: <function View.as_view..view at 0x0000024164BCB310>}
基于方法的类视图:
当需要根据不同请求来实现不同逻辑时,用视图函数需要在内部对请求方法做判断,但若使用方法类视图就可以通过重写其内部方法简单实现;
Flask除了基本类视图,还提供了另一种类视图flask.views.MethodView,在其内部编写的函数方法即是http方法的同名小写映射:
from flask import Flask,render_template,request,views
app = Flask(__name__)
@app.route('/')
def hello_world():
return render_template('index.html')
# 定义LoginView类
class LoginView(views.MethodView):
# 定义get函数
def get(self):
return render_template("index.html")
# 定义post函数
def post(self):
username = request.form.get("username")
password = request.form.get("password")
if username == 'admin' and password == 'admin':
return "用户名正确,可以登录!"
else:
return "用户名或密码错误,不可以登录!"
# 注册类视图
# 未设置endpoint,则endpoint默认为as_view设置的类视图名
app.add_url_rule('/login',view_func=LoginView.as_view('loginview'))
if __name__ == '__main__':
print(app.url_map)
app.run(debug=True)
模板文件index.html为:
Title USERNAME:
PASSWORD:
装饰器的自定义与使用
装饰器本质上是一个python函数,用来修改或扩展其他函数或方法的行为,而不需要修改它们的源代码,可以让其他函数在不需要做任何代码变得的前提下增加额外的功能,其传入参数一般是函数对象(如视图函数),返回值也是一个函数对象;
装饰器主要用于有切面需求的场景,如插入日志、性能测试、事务处理等与函数功能无关的操作,对于这些需要多次重用的代码,可以将其放置在装饰器里,无需在每个函数中反复编写;
如要在新闻页面前插入登录操作可以这样实现:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
# 定义装饰器函数
def user_login(func):
def inner():
# 替代登录操作
print('登录操作!')
# 执行传入的函数对象
func()
# 此处如果return inner(),那么返回的是inner函数的执行结果
# 而使用return inner,则返回的是inner函数
return inner
# 定义新闻页面视图函数news
def news():
print('这是新闻详情页!')
# 将news函数作为参数传给装饰器函数
show_news=user_login(news)
# 因为user_login返回inner函数,所以show_news()==inner()
show_news()
# 打印出show_news的真实函数名(为inner)
print(show_news.__name__)
if __name__ == '__main__':
app.run(debug=True)
运行逻辑:首先将新闻页面函数作为一个参数传给装饰器,装饰器将插入的登录操作与视图函数包装成一个inner函数对象并返回,最后执行该对象便可以实现在新闻页面显示前执行登录操作;
其中登录操作并不是新闻页面函数的功能,且访问每一个新闻页面都应当先执行该操作,固我们将其放置在定义的装饰器中,需要添加该功能的函数使用该装饰器即可;运行结果如下:
登录操作!
这是新闻详情页!
inner
常见标准的装饰器形式:
@user_login
# 定义函数news,该函数将自动被传给装饰器做参数
def news():
print('这是新闻详情页!')
# 此时相当于已经执行完news=user_login(news)
news()
print(news.__name__)
# show_news=user_login(news)
# show_news()
# print(show_news.__name__)
结果相同,news的函数名也已经变为inner:
登录操作!
这是新闻详情页!
inner
不含参数的函数使用装饰器参考如上,对于带参数的函数同样也可以使用装饰器,Python的可变参数:
def func(*args,**kwargs) :
*:代表元组,长度不限;
**:代表键值对,个数不限;
*args:指用元组传参,元组内包含不定个数的位置参数;
**kwargs:指用字典传参,字典内包含不定个数的关键字参数(键值对);
装饰器的使用可以看作是将函数作为入参传递给另一个函数使用,另一个函数内部嵌套函数定义。
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
# 定义装饰器函数
def user_login(func):
# inner函数接收参数
def inner(*args,**kwargs):
print('登录操作!')
# 执行传入函数时使用inner接收到的参数
func(*args,**kwargs)
return inner
# 不带参的不受影响
@user_login
def news():
print(news.__name__)
print('这是新闻详情页!')
news()
# 带参的定义时预声明接收的参数
@user_login
def news_list(*args):
# 获取元组args的第一个元素
page=args[0]
print(news_list.__name__)
print('这是新闻列表页的第'+str(page)+'页!')
# 传递给args的元组即为(5,)
news_list(5)
if __name__ == '__main__':
app.run(debug=True)
运行结果如下:
登录操作!
news
这是新闻详情页!
登录操作!
news_list
这是新闻列表页的第5页!
装饰器不仅可以修改函数的行为,还可以控制函数的返回值,执行其他操作可以修改函数返回的结果。装饰器链,可以将多个装饰器应用到一个函数上,它们会按照从下到上的顺序依次执行。
常见应用场景包括日志记录、权限验证、缓存、性能监控等。