Flask教程2:flask高级视图

发布于:2024-05-06 ⋅ 阅读:(24) ⋅ 点赞:(0)

add_url_rule

欲实现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__)

@app.route('/',endpoint='index')
# 底层其实是使用add_url_rule实现的
def hello_world():
    return 'Hello World!'

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=True)

在这里插入图片描述

类视图的引入

之前我们所定义的视图都是通过函数来实现的,所以称之为视图函数,但其实视图还可以由类来实现,即类视图;

  • 标准类视图:
  • 定义时需要继承flaskviews.View这一基类;
  • 每个类视图内必须包含一个dispatch_request方法,每当类视图接收到请求时都会执行该方法,返回值的设定和视图函数相同;
  • 视图函数可以通过@app.routeapp.add_url_rule来进行注册(映射到url),但类视图只能通过app.add_url_rule来注册,注册时view_func不能直接使用类名,需要调用基类中的as_view方法来为自己取一个“视图函数名”

采用类视图的最大优势,就是可以把多个视图内相同的东西放在父类中,然后子类去继承父类;而类视图不方便的地方,就是每一个子类都要通过一个add_url_rule来进行注册。
下面将创建一个网站包含三个页面,每个页面中都展示相同的对联广告,py文件如下:

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=True)
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
 * Restarting with watchdog (windowsapi)
{'static': <function Flask.__init__.<locals>.<lambda> at 0x000001E8685FFBE0>, 'index': <function View.as_view.<locals>.view at 0x000001E868644C10>, 'login': <function View.as_view.<locals>.view at 0x000001E8686453F0>, 'register': <function View.as_view.<locals>.view at 0x000001E868645480>}
 * Debugger is active!
 * Debugger PIN: 109-993-459

在这里插入图片描述

  • 基于方法的类视图:
    当我们需要根据不同请求来实现不同逻辑时,用视图函数需要在内部对请求方法做判断,但我们使用方法类视图就可以通过重写其内部方法简单实现;
    Flask除了基本类视图,还为我们提供了另一种类视图flask.views.MethodView,在其内部编写的函数方法即是http方法的同名小写映射
装饰器的自定义与使用

装饰器本质上是一个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函数对象并返回,最后执行该对象便可以实现在新闻页面显示前执行登录操作;

刚才我们展示的是不含参数的函数使用装饰器,对于带参数的函数我们同样也可以使用装饰器,这里要先回顾Python的可变参数:

def func(*args,**kwargs) :

*:代表元组,长度不限;
**:代表键值对,个数不限;
*args:指用元组传参,元组内包含不定个数的位置参数;
**kwargs:指用字典传参,字典内包含不定个数的关键字参数(键值对);

可以使用functools.wraps方法来保留原函数的属性与名称,通俗一点理解就是“不换外包装”;
方法的导入:from functools import wraps;
在自定义的装饰器下方添加一行@wraps(<形参名>)即可;

from functools import wraps

# 定义装饰器函数
def user_login(func):
    @wraps(func)
    # inner函数接收参数
    def inner(*args,**kwargs):
        print('登录操作!')
        # 执行传入函数时使用inner接收到的参数
        func(*args,**kwargs)
    return inner
蓝图的使用

上述类视图、装饰器分别通过继承、包装的方式减少了单个flask程序文件里重复代码的出现,实现了程序的优化;

但是这样处理后的文件内,不同功能的代码块(类视图、视图函数)仍然混杂在一起。如果要制作一个非常大型的程序项目,这样不仅会让代码阅读变得十分困难,而且不利于后期维护;

为了解决这一问题,我们需要引入蓝图(flask.Blueprint),用于实现程序功能的模块化;
导入方法:from flask import Blueprint

当接收到请求时,Flask会遍历Flask对象下(已注册)的各蓝图对象,比对蓝图对象中记录的url,比对成功则映射到该url绑定的视图函数并返回响应

  • 主路由视图函数:创建flask对象,并为拓展模块中的蓝图对象提供注册入口
from flask import Flask
from file import news,products

app = Flask(__name__)
@app.route('/')
def hello_world():
    return 'hello my world !'

# 将对应模块下的蓝图对象注册到app中
app.register_blueprint(news.new_list)
app.register_blueprint(products.product_list)

if __name__ == '__main__':
    app.run(debug=True)
  • Blueprint对象的工作方式与Flask对象类似,但其不是一个单独的应用; 每拓展一个蓝图对象,就要在主路由文件下添加一行注册代码;
  • 蓝图对象内记录了当前模块下的所有视图函数,固视图函数不可与蓝图对象同名;
  • 在蓝图内需要通过蓝图对象来定义路由和调用其他装饰器,由蓝图对象定义的路由处于休眠状态,在蓝图被注册时才成为程序的一部分。
  • 模块一:news.py
from flask import Blueprint

# 实例化蓝图对象,参数一类似于蓝图对象的名称
# 一个app下的蓝图对象不可重名
new_list = Blueprint('news',__name__)

# 蓝图对象的使用和app类似
# 一个蓝图下的视图函数名、endpoint不可重复
@new_list.route('/news')
def new():
    return '这是新闻模块!'
  • 模块二:products.py
from flask import Blueprint

# 实例化蓝图对象,参数一类似于蓝图对象的名称
# 一个app下的蓝图对象不可重名
product_list = Blueprint('products',__name__)

# 蓝图对象的使用和app类似
# 一个蓝图下的视图函数名、endpoint不可重复
@product_list.route('/products')
def product():
    return '这是产品模块!'

如此一来,我们便将不同功能的视图函数定义在了不同的模块下,实现了程序的工程化。

在这里插入图片描述

url_prefix设置蓝图前缀

一般在蓝图对象定义时添加,为当前蓝图下的所有视图函数添加统一的前缀,这样不同蓝图下的视图函数的url就不易发生重复;

如下例添加前缀后,加载该新闻模块的url就变为"/index/news":

new_list = Blueprint('news',__name__,url_prefix='/index')

@new_list.route('/news')
def new():
    return '这是新闻模块!'

在这里插入图片描述
如下例中,注册后的新闻模块的url又变为了"/test/news":

app.register_blueprint(news.new_list,url_prefix='/test')

在这里插入图片描述