最近在学习python,以后会分享一些学习历程。
一、介绍
1. 什么是Flask
Flask 是一个用 Python 编写的轻量级 Web 应用框架。
Flask 基于 WSGI(Web Server Gateway Interface)和 Jinja2 模板引擎,旨在帮助开发者快速、简便地创建 Web 应用。
Flask 被称为"微框架",因为它使用简单的核心,用扩展增加其他功能。
详细学习可以参考:Flask 教程 | 菜鸟教程
2. 安装
pip install flask
二、基础知识
1. 简单示例
首先创建一个类,主要做异常处理,打印等
from flask import Flask
class FlaskBase:
app = Flask(__name__)
def run(self):
self.app.run()
新建demo1.py,具体执行
from flask_base import FlaskBase
class Test1(FlaskBase):
@staticmethod
@FlaskBase.app.route("/", methods=['GET']) #使用父类路由
def index():
return "Hello Word!"
if __name__ == '__main__':
test1 = Test1()
test1.run()
打开浏览器访问http://127.0.0.1:5000 ,结果打印"Hello Word!"
2. 路由系统和变量
路由是将URL映射到视图函数的机制。Flask使用装饰器来定义路由
from flask import request, jsonify
@staticmethod
@FlaskBase.app.route("/sayHi1/username>", methods=['GET', 'POST'])
def sayHi1(username):
# 请求url则是/sayHi2/xxx
return username
@staticmethod
@FlaskBase.app.route("/sayHi3", methods=['GET', 'POST'])
def sayHi3():
# 通过request获取参数,请求url则是/sayHi3?username=xxx
if request.method == 'GET':
username = request.args.get('username', type=str)
# 此处欲发送post请求,需要在对应html文件的form表单中设置method为post
elif request.method == 'POST':
username = request.form.get('username')
data = {
'username': username
}
# 输出json
return jsonify(data)
Flask支持在URL中包含变量,类型可以是:
- 字符串(默认):
<username>
- 整数:
<int:post_id>
- 浮点数:
<float:score>
- 路径:
<path:subpath>
- UUID:
<uuid:id>
Flask 路由支持不同的 HTTP 请求方法,如 GET、POST、PUT、DELETE 等。可以通过 methods 参数指定允许的请求方法
3. 请求钩子
@app.before_request
:在每个请求处理之前运行的函数。@app.after_request
:在每个请求处理之后运行的函数。@app.teardown_request
:在请求结束后运行的函数,用于清理工作。
在FlaskBase类中加入
@app.before_request
def before_request():
print('Before request')
@app.after_request
def after_request(response):
print('After request')
return response
@app.teardown_request
def teardown_request(exception):
print('Teardown request')
4. 重定向
url_for:
以帮助我们生成动态的 URL,而无需手动编写不断变化的 URL 地址。它的基本语法如下:
url_for(endpoint, **values)
其中,endpoint
参数是一个视图函数的名称,用于指示要生成 URL 的视图函数。values
参数是一些可选的关键字参数,用于传递给视图函数的参数。通过传递这些参数,我们可以在生成的 URL 中包含相应的参数值。
@staticmethod
@FlaskBase.app.route("/sayHi1/<string:username>", methods=['GET', 'POST'])
def sayHi1(username):
# 重定向
return redirect(url_for('index'))
如果是index重定向sayHi1呢,这里需要使用endpoint。
endpoint:
说明:每个app中都存在一个url_map,这个url_map中包含了url到endpoint的映射;
作用:当request请求传来一个url的时候,会在url_map中先通过rule找到endpoint,然后再在view_functions中根据endpoint再找到对应的视图函数view_func
@staticmethod
@FlaskBase.app.route("/", methods=['GET'])
def index():
return redirect(url_for('sayHi1', username='你好!'))
@staticmethod
@FlaskBase.app.route("/sayHi1/<string:username>", methods=['GET', 'POST'], endpoint='sayHi1')
def sayHi1(username):
return username
或者
def __init__(self):
# 显式注册路由并指定端点名称
FlaskBase.app.add_url_rule('/', 'index', self.index, methods=['GET'])
FlaskBase.app.add_url_rule('/sayHi1/<username>', 'sayHi1', self.sayHi1, methods=['GET', 'POST'])
@staticmethod
def index():
return redirect(url_for('sayHi1', username='你好!'))
@staticmethod
def sayHi1(username):
return username
5. abort函数
- 可以在需要退出请求的地方抛出错误,并结束该请求;
- 我们可以使用errorhandler()装饰器来进行异常的捕获与自定义:
@staticmethod
@FlaskBase.app.route("/login", methods=['GET', 'POST'])
def login():
global username, password
if request.method == 'GET':
username = request.args.get('username', type=str, default="")
password = request.args.get('password', type=str, default="")
# 此处欲发送post请求,需要在对应html文件的form表单中设置method为post
elif request.method == 'POST':
username = request.form.get('username', default="")
password = request.form.get('password', default="")
if username and password:
return 'login successful!'
else:
abort(404)
return None
# 自定义错误处理方法,将404这个error与Python函数绑定
# 当需要抛出404error时,将会访问下面的代码
@staticmethod
@FlaskBase.app.errorhandler(404)
def handle_404_error(err):
# return "发生了错误,错误情况是:%s"%err
# 自定义一个界面
return render_template('404.html')
404.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>页面丢失了</title>
</head>
<body>
<!-- 注意图片文件需要放在一个静态文件夹static里 -->
<img src="../static/error404.png" alt="" width="1530px" height="1530px">
</body>
</html>
目录结构:
your_project/
├── templates/
│ └── 404.html
├── static/
│ └── error404.png
├── flask_base.py
└── demo1.py (包含Test1类的文件)
三、高级视图
1. add_url_rule
欲实现url与视图函数的绑定,除了使用路由装饰器@app.route,还可以通过add_url_rule(rule,endpoint=None,view_func=None)方法,其中:
rule:设置的url
endpoint:给url设置的名称
view_func:指定视图函数的名称
示例在介绍重定向的时候已经给了,可以参考
2. 类视图
标准类视图
- 定义时需要继承flask的views.View这一基类;
- 每个类视图内必须包含一个dispatch_request方法,每当类视图接收到请求时都会执行该方法,返回值的设定和视图函数相同;
- 视图函数可以通过@app.route和app.add_url_rule来进行注册(映射到url),但类视图只能通过app.add_url_rule来注册,注册时view_func不能直接使用类名,需要调用基类中的as_view方法来为自己取一个“视图函数名”
- 采用类视图的最大优势,就是可以把多个视图内相同的东西放在父类中,然后子类去继承父类;而类视图不方便的地方,就是每一个子类都要通过一个add_url_rule来进行注册。
flask_base.py
from flask import Flask, views
# 定义父视图类继承基类View
class ViewBase(views.View):
def __init__(self):
super(ViewBase, self).__init__()
# 实例属性
self.context = {
'ads': '测试view!'
}
from flask import render_template
from flask_base import FlaskBase, ViewBase
# 标准类视图
# 创建应用实例
app_instance = FlaskBase()
# 定义子视图类继承父类并实现工程
class Index(ViewBase):
def dispatch_request(self):
return render_template('view_module/index.html', **self.context)
class Login(ViewBase):
def dispatch_request(self):
return render_template('view_module/login.html', **self.context)
class Register(ViewBase):
def dispatch_request(self):
return render_template('view_module/register.html', **self.context)
app_instance.app.add_url_rule(rule='/', endpoint='index', view_func=Index.as_view('index'))
app_instance.app.add_url_rule(rule='/login/', endpoint='login', view_func=Login.as_view('login'))
app_instance.app.add_url_rule(rule='/register/', endpoint='register', view_func=Register.as_view('register'))
if __name__ == '__main__':
app_instance.run()
基于方法的类视图
- 当需要根据不同请求来实现不同逻辑时,用视图函数需要在内部对请求方法做判断,但使用方法类视图就可以通过重写其内部方法简单实现;
- Flask除了基本类视图,还提供了另一种类视图flask.views.MethodView,在其内部编写的函数方法即是http方法的同名小写映射
from flask import render_template, request, views
from flask_base import FlaskBase
@FlaskBase.app.route('/')
def index():
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 "用户名或密码错误,不可以登录!"
app_instance = FlaskBase()
# 注册类视图
# 未设置endpoint,则endpoint默认为as_view设置的类视图名
app_instance.app.add_url_rule('/login', view_func=LoginView.as_view('loginview'))
if __name__ == '__main__':
print("有效路由:", app_instance.app.url_map)
app_instance.run()
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--action中可以指定表单提交的目标url或文件-->
<!--login指向我们给类视图绑定的url:'/login'-->
<form action="login" method="post">
用户名:
<input type="text" name="username">
<br>
密码:
<input type="password" name="password">
<br>
<!--提交按钮-->
<input type="submit" name="submit">
</form>
</body>
</html>
3. 自定义装饰器
- 装饰器本质上是一个python函数,他可以让其他函数在不需要做任何代码变得的前提下增加额外的功能,其传入参数一般是函数对象(如视图函数),返回值也是一个函数对象;
- 装饰器主要用于有切面需求的场景,如插入日志、性能测试、事务处理等与函数功能无关的操作,对于这些需要多次重用的代码,我们将其放置在装饰器里,就可以无需在每个函数中反复编写(上面请求钩子中已介绍)
from flask_base import FlaskBase
@FlaskBase.app.route('/')
def hello_world():
print('Hello World!')
return 'Hello World!'
# 定义装饰器函数
def user_login(func): # func是被装饰的函数
def inner(): # 定义包裹函数(实际执行的新函数)
print('登录操作!') # 新增的额外功能
func() # 执行原始函数的功能
# 此处如果return inner(),那么返回的是inner函数的执行结果
# 而使用return inner,则返回的是inner函数
return inner
# 定义新闻页面视图函数news
@user_login # 等价于 news = user_login(news)
def news():
print(news.__name__)
print('这是新闻详情页!')
news() # 实际调用的是inner()
app_instance = FlaskBase()
if __name__ == '__main__':
app_instance.run()
执行结果:
登录操作!
inner
这是新闻详情页
这里存在一个问题,就是print(news.__name__)执行结果是inner,另外如果我们的方法需要传参如何实现。
第一个问题,可以使用functools.wraps
方法来保留原函数的属性与名称。直接上代码
from flask_base import FlaskBase
from functools import wraps
@FlaskBase.app.route('/')
def hello_world():
print('Hello World!')
return 'Hello World!'
# 定义装饰器函数
def user_login(func): # func是被装饰的函数
'''
def func(*args, **kwargs):
* :代表元组,长度不限;
** :代表键值对,个数不限;
*args:指用元组传参,元组内包含不定个数的位置参数;
** kwargs:指用字典传参,字典内包含不定个数的关键字参数(键值对);
'''
@wraps(func)
def inner(*args, **kwargs): # inner函数接收参数
print('登录操作!') # 新增的额外功能
# 执行传入函数时使用inner接收到的参数
func(*args, **kwargs) # 执行原始函数的功能
# 此处如果return inner(),那么返回的是inner函数的执行结果
# 而使用return inner,则返回的是inner函数
return inner
# 定义新闻页面视图函数news
@user_login # 等价于 news = user_login(news)
def news():
print(news.__name__)
print('这是新闻详情页!')
news() # 实际调用的是inner()
@user_login
def news_list(*args):
page = args[0]
print(news_list.__name__)
print('这是新闻列表页的第' + str(page) + '页!')
news_list(1) # 实际调用的是inner()
app_instance = FlaskBase()
if __name__ == '__main__':
app_instance.run()
执行结果:
登录操作!
news
这是新闻详情页!
登录操作!
news_list
这是新闻列表页的第1页!
4. 蓝图
上述类视图、装饰器分别通过继承、包装的方式减少了单个flask程序文件里重复代码的出现,实现了程序的优化。但是这样处理后的文件内,不同功能的代码块(类视图、视图函数)仍然混杂在一起。如果要制作一个非常大型的程序项目,这样不仅会让代码阅读变得十分困难,而且不利于后期维护。为了解决这一问题,我们需要引入蓝图(flask.Blueprint),用于实现程序功能的模块化
主路由视图函数:创建flask对象,并为拓展模块中的蓝图对象提供注册入口
from flask_base import FlaskBase
from view_blueprint import News, Product
@FlaskBase.app.route('/')
def hello_world():
print('Hello World!')
return 'Hello World!'
app_instance = FlaskBase()
app_instance.app.register_blueprint(News.new_list)
# url_prefix ,等同于product_list = Blueprint('products', __name__, url_prefix='/index')
app_instance.app.register_blueprint(Product.product_list, url_prefix='/index')
if __name__ == '__main__':
app_instance.run()
分路由视图函数:创建蓝图对象,实现功能拓展
from flask import Blueprint
class News:
# 实例化蓝图对象,参数一类似于蓝图对象的名称
# 一个app下的蓝图对象不可重名
# url_prefix:路由前缀
new_list = Blueprint('news', __name__, url_prefix='/index')
# 蓝图对象的使用和app类似
# 一个蓝图下的视图函数名、endpoint不可重复
@staticmethod
@new_list.route('/news')
def new():
return '这是新闻模块!'
class Product:
# 实例化蓝图对象,参数一类似于蓝图对象的名称
# 一个app下的蓝图对象不可重名
product_list = Blueprint('products', __name__)
# 蓝图对象的使用和app类似
# 一个蓝图下的视图函数名、endpoint不可重复
@staticmethod
@product_list.route('/products')
def new():
return '这是产品模块!'
分别请求: