Flask视图高级技术
1. app.route与add_url_rule简介
在Flask应用中,路由是指用户请求的URL与视图函数之间的映射,处理URL和函数之间关系的程序称为路由。
1.1 app.route的使用
在Flask框架中,默认是使用@app.route装饰器(装饰器只是一种接收函数的函数,并返回一个新的函数。)将视图函数和URL绑定,例如:
@app.route('/')
def hello_world():
return "hello world"
上述代码中,视图函数为hello_world(),使用app.route装饰器会将URL和执行的视图函数的关系保存到app.url_map属性上。
上述代码实现了将URL’/‘与视图函数hello_world()的绑定,我们可以通过url_for(‘hello_world’)反转得到URL’/’,实际上我们可以给这个装饰器再加上endpoint参数(给这个URL命名)。
@app.route('/',endpoint = "index")
def hello_world():
return "hello world"
一旦我们使用了endpoint参数,在使用url_for()反转时就不能使用视图函数名了,而是要用我们定义的URL名。
url_for("index")
from flask import Flask,url_for
app = Flask(__name__)
@app.route('/',endpoint = "index")
def hello_world():
return f'URL反转的内容是:{url_for("index")}'
if __name__ == '__main__':
app.run()
1.2 add_url_rule的使用
除了使用@app.route装饰器,我们还可以使用add_url_rule来绑定视图函数和URL:
from flask import Flask
app = Flask(__name__)
@app.route('/',endpoint = "index")
def hello_world():
return 'hello world'
def my_test():
return "这是测试页"
app.add_url_rule("/test/",endpoint = "my_set",view_func = my_test)
if __name__ == '__main__':
app.run()
在11行代码中,使用app.add_url_rule()函数进行视图函数和URL的绑定,这里将路由“/test/”和视图函数my_test()进行了绑定。要熟悉app.add_url_rule()函数的使用方法,可以查看该函数的原型,首先按住Ctrl键,光标滑过add_url_rule出现超链接时候单击,就可以查看源码了,如图所示。
·rule:设置的URL。
·endpoint:给URL设置的名称。
·view_func:指定视图函数的名称。
因此,我们可以这样用:
因为URL的名称是test而非my_test,所以最终是URL test找到视图函数my_test,URL my_test找不到视图函数报错。
上面的代码中endpoint其实只是指定了此URL的名称,view_func里面指定视图函数的名称。如果已经指定了endpoint,url_for指定的时候,就不能用视图函数的名称,直接用endpoint的名称。如果想用url_for反转的话,也是url_for(endpoint)。
实际上我们看@app.route这个装饰器的源码,也是用add_url_rule,如图所示。
下面验证一下add_url_rule的使用。
from flask import Flask,url_for
app = Flask(__name__)
@app.route('/',endpoint = "index")
def hello_world():
return 'Hello World!'
def my_test():
return "这是测试页"
app.add_url_rule(rule="/test/",endpoint="test",view_func=my_test)
with app.test_request_context():
print(url_for("test"))
if __name__ == '__main__':
app.run(debug=True)
代码解读:
01行导入相应模块;
03行表示Flask初始化;
06行定义路由;
06行定义视图函数;
08行表示返回值;
10行表示定义视图函数;
11行定义返回值;
12行定义路由、endpoint端点为test,视图函数为my_test;
14行表示构建了一个虚拟的请求上下文环境;
15行表示打印输出;
17行表示当模块被直接运行时,代码将被运行,当模块被导入时,代码不被执行;
18行表示开启调试模式。
注意:Flask是通过endpoint找到viewfunction(视图函数)的。
2. Flask类视图
之前我们接触的视图都是函数,所以一般简称为视图函数。其实视图函数也可以基于类来实现,类视图的好处是支持继承,编写完类视图需要通过app.add_url_rule(url_rule,view_func)来进行注册。Flask类视图一般分为标准类视图和基于调度方法的类视图。
2.1 标准类视图
标准类视图的特点:
·必须继承flask.views.View。
·必须实现dispatch_request方法,以后请求过来后,都会执行这个方法,这个方法的返回值相当于之前的视图函数,也必须返回Response或者子类的对象,或者是字符串、元组。
·必须通过app.add_url_rule(rule,endpoint,view_func)来做URL与视图的映射,view_func参数需要使用as_view类方法转换。
·如果指定了endpoint,那么在使用url_for反转时就必须使用endpoint指定的那个值。如果没有指定endpoint,那么就可以使用as_view(视图名称)中指定的视图名称来作为反转。
注意:使用类视图的好处是支持继承,可以把一些共性的东西放在父类中,其他子类可以继承,但是类视图不能跟函数视图一样,写完类视图还需要通过app.add_url_rule(url_rule,view_func)进行注册。
如果一个网站有首页、注册页和登录页面,每个页面要求放置一个同样的对联广告,使用类视图函数如何实现呢?
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
这是广告页{{ ads }}
</body>
</html>
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
这是登录页面{{ ads }}
</body>
</html>
register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
这是注册页面{{ ads }}
</body>
</html>
app.py
from flask import Flask,render_template,views
app = Flask(__name__)
class Ads(views.View):
def __init__(self):
super().__init__()
self.context = {"ads":"这是对联广告"}
class Index(Ads):
def dispatch_request(self):
return render_template("index.html",**self.context)
class Login(Ads):
def dispatch_request(self):
return render_template("login.html",**self.context)
class Register(Ads):
def dispatch_request(self):
return render_template("register.html",**self.context)
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__':
app.run()
2.2 基于方法的类视图
利用视图函数实现不同的请求执行不同的逻辑时比较复杂,需要在视图函数中进行判断,如果利用方法视图实现就比较简单。Flask提供了另外一种类视图flask.views.MethodView,对每个HTTP方法执行不同的函数(映射到对应方法的小写的同名方法上)。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style type="text/css">
.div1{
height:180px;
width:380px;
border:1px solid #8A8989;
margin:0 auto;
}
.input{
display:block;
width:350px;
height:40px;
margin:10px auto;
}
.button{
background:#2066C5;
color:white;
font-size:18px;
font-weight:bold;
height:50px;
border-radius:4px;
}
</style>
</head>
<body>
<div class = "div1">
<form action="login" method = "post">
{#表单开始#}
<input type="text" class = "input" name = "username" placeholder = "请输入用户名">
<input type="password" class = "input" name = "pwd" placeholder="请输入密码">
<input type="submit" value = "登录" class = "input button">
</form>
</div>
{#表单定义完毕#}
</body>
</html>
app.py
from flask import Flask,render_template,request,views
app = Flask(__name__)
@app.route('/')
def hello_world():
return render_template("index.html")
class LoginView(views.MethodView):
def get(self):
return render_template("index.html")
def post(self):
username = request.form.get("username")
password = request.form.get("pwd")
if username == "admin" and password == "admin":
return "用户名正确,可以登录"
else:
return "用户名或密码错误,请重新登录"
app.add_url_rule("/login",view_func = LoginView.as_view("loginview"))
if __name__ == '__main__':
app.run()
在地址栏输入http://127.0.0.1:5000/,并输入用户名和密码(用户名和密码都为admin),然后单击“登录”按钮,运行结果如图所示。
3. Flask装饰器
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能,装饰器的返回值也是一个函数对象。装饰器经常用于有切面需求的场景,比如插入日志、性能测试、事务处理、缓存和权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,可以抽离出大量与函数功能无关的雷同代码并继续重用。
3.1 装饰器的定义和基本使用
app.py
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
@user_login
def news():
print("这是新闻详页")
news()
if __name__ == '__main__':
news()
app.run(debug=True)
代码解读:
01行表示导入Flask模块;
03行表示Flask初始化;
05行表示定义路由;
06行表示定义视图函数;
07行表示返回值;
09行表示定义视图函数;
10行表示定义inner()函数;
11行表示打印输出;
12行表示执行func函数;
13行表示返回inner()函数;
14行表示在视图函数news()之前直接加@user_login,就实现了装饰器的使用。
15行表示定义函数news();
16行打印输出;
18行表示执行news()函数;
03~13行定义了多个函数,这些函数给出了定义,但是没有被调用,最终函数是不会被执行的;
18行表示执行news()函数,news()函数此时的值为inner()函数,那么实质为执行inner()函数,首先打印“登录操作”,然后再执行func()函数,func()函数其实就是news()函数,打印输出“这是新闻详情页”。
运行上面的程序,输出结果如下:
3.2 对带参数的函数使用装饰器
有时给函数加装饰器的时候,这个函数是需要传递参数的,那么就涉及对带参数的函数使用装饰器的问题。首先来看Python中函数的可变参数的例子。
3.2.1 函数的可变参数
def func(*args,**kwargs):
*:代指元组,长度不限;
**:代表键值对,个数不限。
*args:代表元祖,长度不限制。
**kwargs:代表按键值对,个数不限。
代码解读:
01行表示定义函数func(),该函数可接收可变参数;
02行打印输出args参数的长度;
03行打印输出args参数;
04行遍历kwargs参数;打印输出kwargs参数的值;
06行表示给func()函数传递参数,传递了0个args参数,传递了0个键值对作为kwargs参数;
07行表示输出100个 = ,分割;
08行表示给func()函数传递参数,传递了3个args参数,传递了2个键值对作为kwargs参数;
3.2.2 对带参数的函数使用装饰器
带参数的函数使用装饰器示例如下:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
def user_login(func):
def inner(*args,**kwargs):
print("\n登录操作\n")
func(*args,**kwargs)
return inner
@user_login
def news():
print(f"此时函数名:{news.__name__}")
print("这是新闻详情页")
news()
@user_login
def news_list(*args):
page = args[0]
print(f"此时函数名:{news_list.__name__}")
print(f"这是新闻列表页的第{str(page)}页")
news_list(5)
if __name__ == '__main__':
app.run()
代码解读:
01行表示导入Flask模块;
03行表示Flask初始化;
04行定义路由;
05行定义视图函数;
06行表示返回值;
08行表示定义函数user_login();
10行表示定义内部函数inner();
11行表示打印输出;
13行定义返回inner();
15行表示使用装饰器;
16行定义函数news();
17行表示打印输出此时的函数名称;
18行表示打印输出;
22行表示使用装饰器;
23行表示定义函数news_list;
24行表示元组args[0]赋值给page;
25行表示打印输出函数名;
26行表示打印输出;
28行表示调用函数news_list();
29行表示当模块被直接运行时,代码将被运行,当模块是被导入时,代码不被执行。
以上代码还存在一点问题,在调用过程中会改变原来的名称,不管是news()函数还是news_list()函数,最终执行时被替换成了inner()函数。为避免出现此种情况,可以使用functools.wraps在装饰器的函数上对传进来的函数进行包裹,这样就不会丢失原始函数了。
注意:导入包wraps,使用命令为from functools import wraps。
修改的代码如下:代码解读看注释
### 导入Flask模块
from flask import Flask
### 导入包wraps
from functools import wraps
### Flask初始化
app = Flask(__name__)
### 定义路由
@app.route('/')
### 定义函数
def hello_world():
### 返回值
return 'Hello World!'
### 定义登录装饰器,使用func接收函数名作为形参
def user_login(func):
### 使用functools.wraps对传来的参数进行包裹。
@wraps(func)
### 定义inner函数
def inner(*args,**kwargs):
print("\n登录操作\n")
### 执行func函数
func(*args,**kwargs)
### 返回inner函数名
return inner
# 使用登录装饰器
@user_login
def news():
### 输出函数名
print(f"此时函数名:{news.__name__}")
print("这是新闻详情页")
#调用news函数
news()
# 登录认证装饰器
@user_login
### 定义news_list函数
def news_list(*args):
### 元组首元素赋值
page = args[0]
### 输出函数名
print(f"此时函数名:{news_list.__name__}")
print(f"这是新闻列表页的第{str(page)}页")
# 调用函数
news_list(5)
if __name__ == '__main__':
app.run()
输出结果如下:
4. 蓝图
随着业务代码的增加,将所有代码都放在单个程序文件中是非常不合适的。这不仅会让阅读代码变得困难,而且会给后期维护带来麻烦。Flask蓝图提供了模块化管理程序路由的功能,使程序结构清晰、简单易懂。一个程序执行文件中,如果功能代码过多,是不方便后期维护的。如何实现程序代码模块化,根据具体不同功能模块的实现,划分成不同的分类,降低各功能模块之间的耦合度呢?这时flask.Blueprint(蓝图)就派上用场了。蓝图的定义:在蓝图被注册到应用之后,所要执行的操作的集合。当分配请求时,Flask会把蓝图和视图函数关联起来,并生成两个端点之前的URL。
蓝图的目的是实现各个模块的视图函数写在不同的py文件中。在主视图中导入分路由视图的模块,并且注册蓝图对象。
注意:视图函数的名称不能和蓝图对称的名称一样。
上代码:
app.py
from flask import Flask
import news,produt
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
# 将news模块里的蓝图对象new_list注册到app
app.register_blueprint(news.new_list)
# 将products模块里的蓝图对象product_list注册到app
app.register_blueprint(produt.product_list)
if __name__ == '__main__':
app.run()
news.py
#encoding:utf-8
from flask import Blueprint
# 创建一个Blueprint对象。第一个参数是Blueprint对象的姓名。
# 在一个app里,姓名不能与其余的Blueprint对象姓名重复
# 第二个参数__name__用作初始化
new_list = Blueprint("new",__name__)
# 将蓝图对象当做app那样使用
@new_list.route("/news")
# 定义函数new
def new():
return "这是新闻模块"
product.py
#encoding:utf-8
# 导入蓝图Blueprint
from flask import Blueprint
# 创建一个蓝图Blueprint对象
product_list = Blueprint("products",__name__)
# 将蓝图对象当做app那样使用。/products是URL
@product_list.route("/products")
def product():
return "这是产品模块"
蓝图的目的是实现各个模块的视图函数写在不同的py文件中。在主视图中导入分路由视图的模块,并且注册蓝图对象。