Flask
1、Flask介绍
安装依赖:pip install flask
1.1、Flask概述
Flask 是一个轻量级的 Python Web 框架,由 Armin Ronacher 开发。它被设计为简单而灵活的工具,用于快速构建 Web 应用程序和 API。Flask 的设计理念是尽可能地简单、易于理解和扩展,同时保持灵活性和可定制性。它采用了轻量级的核心和模块化的架构,允许开发者根据项目的需求选择性地添加功能
1.2、Flask优点
① 简单易学: Flask 的设计简单直观,容易上手,对于初学者来说学习曲线较低
② 轻量级: Flask 的核心库非常小巧,没有过多的依赖,因此运行效率高
③ 灵活性: Flask 的架构非常灵活,开发者可以根据需要选择性地添加功能,而不受框架本身的限制
④ 可扩展性: Flask 支持使用扩展包来添加额外的功能,例如用户认证、数据库集成等
⑤ 文档丰富: Flask 拥有丰富的官方文档和社区支持,开发者可以很容易地找到所需的帮助和资源
1.3、Flask缺点
① 过于灵活: 有时候灵活性可能会导致开发者需要自行选择和配置各种功能,增加了开发的复杂度
② 不适合大型应用: Flask 是一个微框架,对于大型复杂的应用程序来说,可能需要开发者自行处理一些问题,如结构组织、性能优化等,对于大型复杂的应用程序可以使用 Django 框架进行开发
1.4、Flask和Django的区别
① 复杂度: Django 是一个全功能的高级 Web 框架,提供了大量的内置功能和组件,适用于构建大型、复杂的 Web 应用。而 Flask 更注重的是简单和灵活,更适合于快速开发小型项目或者原型
② 组件和功能: Django 自带了很多功能,如 ORM、认证系统、管理后台等,而 Flask 的核心功能相对较少,需要通过扩展来实现类似的功能
③ 学习曲线: Django 的学习曲线相对于 Flask 来说可能更陡峭一些,因为它提供了更多的功能和约定。相比之下,Flask 的学习曲线相对较低
④ 适用场景: Django 适用于构建大型、复杂的 Web 应用,尤其是企业级应用。而 Flask 更适合于快速原型开发、小型项目或者需要高度定制的应用
1.5、Flask初体验
from flask import Flask
app = Flask(__name__) # 创建一个app
@app.route('/') # 装饰器
def hello_world(): # 视图函数
return 'hello world'
if __name__ == "__main__":
app.run() # 运行服务 默认是监听本机的5000端口
Tip:可以在run中传入debug=True,即 app.run(debug=True),以开启调试模式,这样在服务运行期间修改代码就可以不用重启服务
2、基础篇
2.1、路由创建
@app.route()的参数 | 描述 |
---|---|
第一个位置参数 | 请求路径 静态参数路由举例:/index 动态参数路由举例:/index/<int:id> |
methods | 当前视图函数支持的请求方式,请求方式不区分大小写,默认为GET |
endpoint | 指定路由的名称,在路由十分长的时候endpoint就显得十分好用,默认为路由对应的视图函数名 |
defaults | 默认参数设置,必须在视图函数中定义一个形参来接收 |
redirect_to | 用于设置永久重定向 |
strict_slashes | 设置路由路径匹配是否为严格模式 —— 指定路由的斜杠严格性,默认不设置为严格路由匹配模式 当 strict_slashes 设置为 True 时,Flask 会严格区分 URL 的末尾是否有斜杠 |
from flask import Flask, render_template, request, session, redirect, url_for
# 创建一个 Flask 应用程序的实例
app = Flask(__name__)
# 静态路由
@app.route('/') # http://127.0.0.1:5000/
def index(): # 视图函数
return 'Hello World!' # 返回字符串
# 动态路由
@app.route('/hello/<string:name>/<int:age>') # http://127.0.0.1:5000/hello/niki/23
def hello(name, age):
return f'Hello {
name},your age is {
age}'
# 动态路由(指定变量)
@app.route('/hi/name=<string:name>') # http://127.0.0.1:5000/hi/name=niki
def hi(name):
return f'Hi {
name}'
# methods请求方法
@app.route('/login', methods=['GET', 'POST']) # http://127.0.0.1:5000/login
def login():
if request.method == 'POST':
print('使用了POST方法')
elif request.method == 'GET':
print('使用了GET方法')
# 给路由起名endpoint
@app.route('/test1', endpoint='ep_test1') # http://127.0.0.1:5000/test1
def test1():
print(app.view_functions) # endpoint和视图函数的映射:{'static': <function Flask.__init__.<locals>.<lambda> at 0x00000251C3FCFE18>, 'ep_test1': <function test1 at 0x00000251C5053400>}
print(app.url_map) # endpoint和理由的映射:Map([<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>, <Rule '/test1' (HEAD, OPTIONS, GET) -> ep_test1>])
return 'test1'
# 默认参数defaults
@app.route('/test2', defaults={
'name': 'niki'}) # http://127.0.0.1:5000/test2
def test2(name):
return 'My name is {}'.format(name)
# 重定向redirect_to
@app.route('/test3', redirect_to='/') # http://127.0.0.1:5000/test3
def test3():
return '起始页'
# 严格模式strict_slashes
@app.route('/strict_true',strict_slashes=True) # 报错:http://127.0.0.1:5000/strict_true/ 通过:http://127.0.0.1:5000/strict_true/
def strict_true():
return f'严格模式匹配{
url_for("strict_true")}'
@app.route('/strict_false/',strict_slashes=False) # 通过:http://127.0.0.1:5000/strict_false/ 通过:http://127.0.0.1:5000/strict_false/
def strict_false():
return f'非严格模式路由匹配{
url_for("strict_false")}'
if __name__ == '__main__':
app.run() # 启用Flask应用程序
注意点 ① :在指定变量名的动态路由中,以name=<string:name>为例,第一个name是 URL 中的变量名,第二个 name 是 Flask 路由中的一种类型转换器 string 的名字,它用于将 URL 中指定的 name 变量的值转换为字符串类型, URL 中的变量名跟类型转换器的名字可以不同,但是类型转换器的名字需要与视图函数的参数名相同,例如:
from flask import Flask, render_template, request, session, redirect, url_for
# 创建一个 Flask 应用程序的实例
app = Flask(__name__)
@app.route('/hi/name=<string:nn>') # http://127.0.0.1:5000/hi/name=niki
def hi(nn):
return f'Hi {
nn}'
if __name__ == '__main__':
app.run() # 启用Flask应用程序
注意点 ② :除了使用原有的类型转换器,可以使用自定义转换器
安装依赖:pip install werkzeug(Werkzeug 是 Flask 的底层 WSGI 工具库,它提供了一些信号,允许在应用程序处理请求的不同阶段插入自定义代码)
from flask import Flask, render_template, request, session, redirect, url_for
from werkzeug.routing import BaseConverter
# 创建一个 Flask 应用程序的实例
app = Flask(__name__)
# 用于匹配 URL 中的正则表达式
class RegexConverter(BaseConverter):
def __init__(self, url_map, regex):
# 重写父类定义方法
super(RegexConverter, self).__init__(url_map)
self.regex = regex
def to_python(self, value): # 重写了父类方法,用于将匹配到的字符串值转换为 Python 对象
print('to_python方法被调用')
return value
# 将自定义转换器注册到 Flask 应用程序的转换器字典中
app.url_map.converters['re'] = RegexConverter
@app.route("/index/<re('1\d{4}'):value>") # 匹配一个以数字1开头、后面跟着4位数字的字符串,匹配到的字符串值会被传递给路由函数的参数value
def index(value):
print(value)
return "Hello World!"
if __name__ == '__main__':
app.run() # 启用Flask应用程序
注意点 ③ :url_map 是一个 Werkzeug 的 Map 对象,它包含了 Flask 应用程序中所有路由规则的信息;view_functions 是一个字典,它将路由规则与视图函数进行了映射
from flask import Flask, render_template, request, session, redirect, url_for
app = Flask(__name__)
@app.route('/getinfo/<string:name>', endpoint='getinfo')
def getinfo(name):
return f'Hello {
name}'
@app.route('/login')
def login():
return "登录页面"
if __name__ == '__main__':
print('-' * 15, 'url_map', '-' * 15, '\n', app.url_map)
print('-' * 15, 'view_functions', '-' * 15, '\n', app.view_functions)
print('-' * 60)
app.run() # 启用Flask应用程序
#运行结果
--------------- url_map ---------------
Map([<Rule '/login' (OPTIONS, HEAD, GET) -> login>,
<Rule '/getinfo/<name>' (OPTIONS, HEAD, GET) -> getinfo>,
<Rule '/static/<filename>' (OPTIONS, HEAD, GET) -> static>])
--------------- view_functions ---------------
{
'static': <function Flask.__init__.<locals>.<lambda> at 0x0000028ACA54FE18>, 'getinfo': <function getinfo at 0x0000028ACB5C21E0>, 'login': <function login at 0x0000028ACB5C2F28>}
------------------------------------------------------------
注意点 ④ :可以add_url_rule(rule,endpoint=None,view_func=None)来注册路由
from flask import Flask, render_template, request, session, redirect, url_for, make_response, abort
app = Flask(__name__)
def index():
return "Hello World!"
app.add_url_rule('/', view_func=index)
if __name__ == '__main__':
app.run(debug=True)
2.2、url_for 实现 URL 反转
URL 反转是一种 Web 开发中常用的技术,用于根据路由规则和参数生成对应的 URL,在 Flask 中,url_for 函数就是用于实现 URL 反转的工具。通过调用 url_for 函数并传递相应的视图函数名称以及需要的参数,可以动态地生成与该视图函数对应的 URL
URL 反转的好处在于,可以通过视图函数找到对应的URL ,这对于URL需要经常发生变化或者URL非常长的场景中十分实用
from flask import Flask, render_template, request, session, redirect, url_for, make_response, abort
from urllib.parse import unquote
app = Flask(__name__)
@app.route('/') # 普通
def index():
return 'Hello World'
@app.route('/profile/<username>') # 携带参数
def getusername(username):
return f'My name is {
username}'
@app.route('/a/b/c/d/e/f/g?name=<username>', endpoint="profile") # 使用endpoint
def profile(username):
return f'{
username}\'s profile'
if __name__ == '__main__':
with app.test_request_context():
print(url_for('index'))
print(url_for('getusername', username='niki'))
print(unquote(url_for("profile", username="niki"))) # 对URL中的特殊字符进行解码
#运行结果
/
/profile/niki
/a/b/c/d/e/f/g?name=niki
test_request_context() :是 Flask 提供的一个上下文管理器,用于模拟请求环境,它允许在测试环境中调用 Flask 视图函数,而无需实际发送 HTTP 请求,通常在编写 Flask 应用程序的单元测试时,若希望测试视图函数的行为,但不希望通过发送 HTTP 请求来进行测试,那么在这种情况下可以使用 test_request_context() 来创建一个虚拟的请求上下文,以便在其中调用视图函数
url_for解析出来的路径中的问号、等于号会被编码,需要通过urllib.parse.unquote进行解码
2.3、响应
2.3.1 响应字符串
from flask import Flask, render_template, request, session, redirect, url_for, make_response, abort
app = Flask(__name__)
@app.route('/')
def index():
return "Hello World!" # 响应字符串
if __name__ == '__main__':
app.run(debug=True)
2.3.2 重定向
状态码 | 描述 |
---|---|
301 Moved Permanently (永久重定向) |
当服务器返回状态码为 301 时,表示请求的资源已经永久移动到了新的位置 客户端收到 301 响应后会自动更新书签和缓存,并且以后的请求会直接发送到新的 URL 这种重定向通常用于网站进行结构调整或更改域名时,告知搜索引擎新的 URL 地址 |
302 Found (临时重定向) |
当服务器返回状态码为 302 时,表示请求的资源暂时移动到了一个新的位置 客户端收到 302 响应后会暂时重定向到新的 URL,但不会更新书签和缓存,后续请求仍然会发送到原始 URL 这种重定向通常用于临时性的页面重定向,比如暂时性的维护或临时性的资源移动 |
308 Permanent Redirect (永久重定向) |
当服务器返回状态码为 308 时,表示请求的资源已经永久移动到了新的位置 与 301 相比,308 明确指示客户端在重定向时不得更改请求方法,即若原始请求是 POST,则在重定向后应该继续使用 POST 这种状态码可以确保在进行重定向时不会丢失原始请求的请求体(例如 POST 请求的表单数据) |
from flask import Flask, render_template, request, session, redirect, url_for, make_response, abort
app = Flask(__name__)
@app.route('/')
def index():
return "Hello World!"
@app.route('/a', redirect_to='/') # 在路由装饰器中指定redirect_to 状态码是308 表示永久重定向
def a():
return "a"
@app.route('/b')
def b():
return redirect('/') # return一个redirect 默认状态码是302 表示临时重定向
@app.route('/c')
def c():
return redirect('/', code=301) # 指定状态码为301,表示永久重定向
@app.route('/d')
def d():
return redirect(url_for('index')) # 除了使用路径,还可以使用url_for(视图函数名) 同样的默认状态码是302
if __name__ == '__main__':
app.run(debug=True)
在浏览器中,如果访问一个会导致重定向的路由,如果状态码是301、308则再次访问时,状态码会变成200,这是因为对于永久重定向来说,客户端会对其进行缓存,302则反之,如果不想要缓存的话,可以在web开发者工具中勾选“禁用缓存”
2.3.3 响应模板
① 概述:Flask 不包含模板引擎,而是选择了与其配合使用的 Jinja2 模板引擎,在视图函数中使用render_template()函数来给前端响应模板
① 模板引擎:用于生成动态内容的软件组件或系统,其通常接受模板和数据作为输入,并生成包含动态内容的输出文本
② Jinja2:是一个功能强大的 Python 模板引擎,由 Pocoo 开发,它提供了一种灵活而强大的方式来生成动态内容,包括 HTML、XML、JSON 等,Jinja2 支持模板继承、条件判断、循环迭代等高级特性,使得模板编写更加简洁和灵活,例如可以将HTML中的 { { 变量名 }} 解析成后端传入的变量值
② 文件结构:在 Flask 框架中,模板需要放置在templates中,这样在使用render_template ( 模板名 ) 的时候才能找到相应的 html 文件,例如:
根目录/
├── templates/
│ └── tpl.html
└── test.py
③ 模板内容:模板要符合Jinja模板的写法,例如变量要使用{ { 变量 }}、条件语句要使用{% if 条件 %}…{% endif %}、过滤器要在{ { }}中使用管道符“ | ” 等,例如编写tpl.html如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% if username %}
<!-- 接收变量 -->
<h1>Hello, {
{ username }}!</h1>
{% endif %}
{% if age %}
<h1>My age is {
{ age }}, my gender is {
{ other.gender }}</h1>
<!-- 使用过滤器 -->
<h1>My hobby is {
{ other.hobby|join(",") }}</h1>
{% endif %}
</body>
</html>
④ 视图函数:视图函数中要使用 return render_template(模板名) 来返回一个模板,该模板首先会交给Jinja引擎进行渲染以动态地生成内容,然后再返回给客户端,例如在test.py中编写如下:
from flask import Flask, render_template, request, session, redirect, url_for, make_response, abort
app = Flask(__name__)
@app.route('/')
def index():
username = 'niki' #
info = {
'age': 23,
'other': {
'gender': 'male',
'hobby': ['唱', '跳', 'rap', '篮球']
}
}
return render_template('tpl.html', username=username, **info)
if __name__ == '__main__':
app.run(debug=True)
运行结果:
2.4、蓝图
① 概述:在 Flask 中,蓝图(Blueprint)是一种组织和管理应用程序路由和视图函数的机制。它允许将相关功能的路由、视图函数和静态文件组织到单独的模块中,以便更好地组织和管理代码
Blueprint ( name,import_name,url_prefix )
第一个参数是蓝图对象的名字,第二个参数是模块名,第三个参数是设置路由的前缀
② 文件结构
根目录/
├── source/ # 软件包
│ ├── __init__.py # 用于创建 Flask 应用程序,做全局配置、初始化等工作
│ ├── static/ # 存放静态文件,例如css、js、图片等
│ ├── templates/ # 存放模板文件
│ └── views/ # 存放视图函数的模块文件
│ ├── country.py # 包含处理与国家相关的请求的视图函数
│ └── animal.py # 包含处理与动物相关的请求的视图函数
├── utils/ # 存放工具模块
└── app.py # 项目的入口文件
③ 在animal.py、country.py中创建蓝图对象以及定义蓝图中的路由
#country.py
from flask import Blueprint
# 创建了个 Blueprint 对象,命名为 "book",并指定了模块的名称为 __name__,表示当前模块
# 使用 url_prefix 参数设置了 Blueprint 的 URL 前缀为 "/country"。这意味着后续定义的路由都会以 "/country" 开头
ct = Blueprint("country", __name__, url_prefix="/country")
# 定义了三个路由和视图函数
@ct.route("/")
def index():
return "Hello Country!"
@ct.route("/China")
def China():
return "Hello China!"
@ct.route("/France")
def France():
return "Hello France!"
#animal.py
from flask import Blueprint
am = Blueprint("animal", __name__, url_prefix="/animal")
@am.route("/")
def index():
return "Hello Animal!"
@am.route("/Dog")
def Dog():
return "Hello Dog!"
@am.route("/Cat")
def Cat():
return "Hello Cat!"
④ 在source/ __init__ .py编写创建flask实例的函数create_app(),并在 flask 实例中注册两个蓝图,这样两个蓝图的路由将被引用到 flask 实例 app 身上
from flask import Flask
from .views import animal, country
def create_app():
# 创建了一个 Flask 应用实例
app = Flask(__name__)
# 使用 source.register_blueprint() 方法注册 views 模块中的 animal 和 country 蓝图对象
# 这样,animal 蓝图中的路由和视图函数以及 country 蓝图中的路由和视图函数就成为了应用程序的一部分
app.register_blueprint(animal.am)
app.register_blueprint(country.ct)
# 返回
return app
⑤ 在app.py这个项目入口文件中运行 flask 实例,这样就可以像该服务发送请求了
from source import create_app
app = create_app()
if __name__ == '__main__':
app.run() # 运行后访问http://127.0.0.1:5000/country/China
需要注意的是:source/ __init__ .py中要引用views中的模块,from .views(views前有一点) 而不是from views(views前没有一点),这是因为我们在app.py处运行程序,如果写from views的话,则views会从app.py所在的目录(即根目录)出发找,即这个views意为与根目录/app.py同级的文件夹,如果找不到就会报错;而from .views意为source.views,要达到这种效果,要保证写from .views的文件是一个软件包(有__init__.py的文件夹)的直接子文件且不是直接运行该文件,这样 . 就会被翻译成软件包,显然.views被翻译成source.views之后,app.py首先能找到与之同级的source,再能找到source下的views,也就不会报错了
拓展:如果直接运行source/__init__.py则会报错,因为直接运行该文件了,此时.views会被翻译成__main__.views,也就是会把当前的文件当作一个文件夹,然后去找文件夹下的views,显然运行的文件怎么会是文件夹呢,所以因为改成views(前面不要一点),此时就会被翻译成source/__init__.py所在文件夹下的views,也就不会报错了
2.5、蓝图子域名
类型 | 举例 |
---|---|
主域名设置 | app.config[‘SERVER_NAME’]=‘hyh.com:5000’ |
① 以管理员方式打开cmd,输入notepad打开记事本,打开位于 C:\Windows\System32\drivers\etc 下的域名重定向文件 hosts,为里面添加一条记录,例如127.0.0.1 hyh.com(大家可以自定义设置),这样在访问域名hyh.com解析为本主机127.0.0.1了
② 在上面的source/__init__.py文件中设置app.config[‘SERVER_NAME’]来指定主域名,便可以通过http://hyh.com:5000/来进行访问
from flask import Flask
from .views import animal, country
def create_app():
# 创建了一个 Flask 应用实例
app = Flask(__name__)
# 添加一个路由
@app.route('/')
def index():
return 'Hello World!'
# 使用 source.register_blueprint() 方法注册 views 模块中的 animal 和 country 蓝图对象
# 这样,animal 蓝图中的路由和视图函数以及 country 蓝图中的路由和视图函数就成为了应用程序的一部分
app.register_blueprint(animal.am)
app.register_blueprint(country.ct)
# 设置主域名
app.config['SERVER_NAME'] = 'hyh.com:5000'
# 返回
return app
Tip:如果打开了vpn的话,可能需要先关掉一下,不然VPN 可能会拦截对 hyh.com 的 DNS 解析请求,并将其重定向到 VPN 提供的 DNS 服务器,而所写的 Flask 应用实际上只在本地主机上运行,VPN 不知道该域名应该解析到哪里,因此可能无法正确访问 hyh.com
类型 | 举例 |
---|---|
子域名 | bp=Blueprint(‘admin’,name,subdomain=‘admin’) |
① 同样需要在host文件中插入一条127.0.0.1 niki.hyh.com的记录,niki便作为hyh.com下的子域名
② 以animal.py为例,在创建蓝图对象时传入subdomain参数值以设置子域名,便可以通过http://niki.hyh.com:5000/来进行访问
from flask import Blueprint
am = Blueprint("animal", __name__, url_prefix="/animal", subdomain="niki")
@am.route("/")
def index():
return "Hello Animal!"
@am.route("/Dog")
def Dog():
return "Hello Dog!"