Flask中@app.route

本文通过实例详细解析 Flask 框架中 @app.route() 的工作原理,包括装饰器及装饰器工厂的运用,逐步构建 NotFlask 类来模拟 Flask 的路由功能。
本文我们先来说说Flask,深入探讨Flask如何实现在函数上方写“@app.route()”就能在因特网上输出函数的执行结果。

下面是Flask主页给我们的第一个例子,我们现在就由它入手,深入理解“@app.route()”是如何工作的。
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

 

@app.route和其它装饰器

要想明白“@app.route()”的工作原理,我们首先需要看一看Python中的装饰器(就是以“@”开头的那玩意,下面接着函数定义)。

究竟什么是装饰器?没啥特别的。装饰器只是一种接受函数(就是那个你用“@”符号装饰的函数)的函数,并返回一个新的函数。

当你装饰一个函数,意味着你告诉Python调用的是那个由你的装饰器返回的新函数,而不仅仅是直接返回原函数体的执行结果。

还不是很明白?这里是一个简单的例子:

# This is our decorator
def simple_decorator(f):
    # This is the new function we're going to return
    # This function will be used in place of our original definition
    def wrapper():
        print "Entering Function"
        f()
        print "Exited Function"

    return wrapper

@simple_decorator 
def hello():
    print "Hello World"

hello()

运行上述代码会输出以下结果:

Entering Function
Hello World
Exited Function

很好!

现在我们有点明白怎样创建我们自己的“@app.route()”装饰器了,但你可能会注意到有一个不同点,就是我们的simple_decorator不可以接受任何参数, 但“@app.route()”却可以。

那么我们怎样才能给我们的装饰器传参数?要实现这个我们只需创建一个“decorator_factory”函数,我们调用这个函数,返回适用于我们函数的装饰器。现在看看如果实现它。

def decorator_factory(enter_message, exit_message):
    # We're going to return this decorator
    def simple_decorator(f):
        def wrapper():
            print enter_message
            f()
            print exit_message

        return wrapper

    return simple_decorator

@decorator_factory("Start", "End")
def hello():
    print "Hello World"

hello()

给我们的输出是:

Start
Hello World
End
请注意在我们写@decorator_factory(“Start”, “End”)时,我们实际调用的是decorator_factory函数,实际返回的装饰器已经被用上了,代码很整洁,对吧?

把“app”放进“app.route”

现在我们掌握了装饰器怎样工作的全部前置知识 ,可以重新实现Flask API的这个部分了,那么把我们的目光转移到“app”在我们Flask应用中的重要地位上面来。

在开始解释Flask对象里面发生了什么之前,我们先创建我们自己的Python类NotFlask。

class NotFlask():
    pass

app = NotFlask()

这不是个很有趣的类,不过有一样值得注意,就是这个类的方法也可以被用作装饰器,所以让我们把这个类写得更有趣一点,加一个称作 route的方法,它是一个简单的装饰器工厂。

class NotFlask():
    def route(self, route_str):
        def decorator(f):
            return f

        return decorator

app = NotFlask()

@app.route("/")
def hello():
    return "Hello World!"

这个装饰器和我们之前创建的那些最大的不同,在于我们不想修改被我们装饰的函数的行为,我们只是想获得它的引用。

所以,最后一步是我们打算去利用一个特性,就是用装饰器函数的副产品去保存一个提供给我们的路径之间的链接,装饰器函数应该与它关联起来。

为了实现这个,我们给我们的NotFlask对象加一个“routes”字典,当我们的“decorator”函数被调用,路径将被插入新字典中函数对应的位置。

class NotFlask():
    def __init__(self):
        self.routes = {}

    def route(self, route_str):
        def decorator(f):
            self.routes[route_str] = f
            return f

        return decorator

app = NotFlask()

@app.route("/")
def hello():
    return "Hello World!"

现在我们就要完成了!可如果没法访问内部的视图函数,保存路径的字典又有什么用?让我们加入一个方法serve(path),当给定的路径存在时运行一个函数并给们我结果,当路径尚未注册时则抛出一个异常。

class NotFlask():
    def __init__(self):
        self.routes = {}

    def route(self, route_str):
        def decorator(f):
            self.routes[route_str] = f
            return f

        return decorator

    def serve(self, path):
        view_function = self.routes.get(path)
        if view_function:
            return view_function()
        else:
            raise ValueError('Route "{}"" has not been registered'.format(path))

app = NotFlask()

@app.route("/")
def hello():
    return "Hello World!"

print app.serve("/")

我们会看到:

Hello World!

我们已经完成了一个的Flask网页上第一个例子的非常简单的重现,让我们写一些快速测试检测我们简单重现的Flask的“@app.route()”是否正确。

#-*- coding: UTF-8 -*-
import unittest


class TestNotFlask(unittest.TestCase):
    def setUp(self):
        self.app = NotFlask()

    def test_valid_route(self):
        @self.app.route('/')
        def index():
            return 'Hello World'

        self.assertEqual(self.app.serve('/'), 'Hello World')

    def test_invalid_route(self):
        with self.assertRaises(ValueError):
            self.app.serve('/invalid')

if __name__ == '__main__':  
    # unittest.main() # 用这个是最简单的,下面的用法可以同时测试多个类  
    # unittest.TextTestRunner(verbosity=2).run(suite1) # 这个等价于上述但可设置verbosity=2,省去了运行时加-v  
    suite1 = unittest.TestLoader().loadTestsFromTestCase(TestNotFlask)  
    suite2 = unittest.TestLoader().loadTestsFromTestCase(TestNotFlask)  
    suite = unittest.TestSuite([suite1, suite2])  
    unittest.TextTestRunner(verbosity=2).run(suite)

输出

Hello World!
test_invalid_route (main.TestNotFlask) … ok
test_valid_route (main.TestNotFlask) … ok
test_invalid_route (main.TestNotFlask) … ok
test_valid_route (main.TestNotFlask) … ok

Ran 4 tests in 0.003s

OK

完全正确!所以,仅仅是一个简单的包含一个字典的装饰器, 就重现了Flask的“app.route()”装饰器的基本的行为。

在本系列的下一篇,也是Flask的app.route()的最后一篇,将通过解析下面这个例子来解释动态URL模式是如何工作。

import datetime from flask import Flask as _Flask, flash, redirect from flask import request, session from flask import render_template from flask.json import JSONEncoder as _JSONEncoder, jsonify import decimal import os from service import user_service, notice_service, data_service, output_service, weather_service, predict_service from utils.JsonUtils import read_json from utils.Result import Result base = os.path.dirname(__file__) directory_path = os.path.dirname(__file__) json_path = directory_path + '/static/api/' class JSONEncoder(_JSONEncoder): def default(self, o): if isinstance(o, decimal.Decimal): return float(o) if isinstance(o, datetime.datetime): return o.strftime("%Y-%m-%d %H:%M:%S") if isinstance(o, datetime.date): return o.strftime("%Y-%m-%d") super(_JSONEncoder, self).default(o) class Flask(_Flask): json_encoder = JSONEncoder import os app = Flask(__name__) app.config['SESSION_TYPE'] = 'filesystem' app.config['SECRET_KEY'] = os.urandom(24) # ----------------------------------------------页面加载模块开始---------------------------------------------- # 加载系统json文件 @app.route('/api/<string:path>/') def api_json(path): if path == 'init.json' and session.get('user') and session.get('user')['type'] == 1: path = 'custom_init.json' return read_json(json_path + path) # 加载page下的静态页面 @app.route('/page/<string:path>') def api_path(path): return render_template("page/" + path) # 系统默认路径后台跳转 @app.route('/admin') def admin_page(): if session.get('user') and session.get('user')['id'] > 0: return render_template("index.html") else: return redirect("/login") # 系统可视化数据请求接口 @app.route('/') def main_page(): month_rain = data_service.get_month_rain_volume() ave_wind = data_service.get_ave_wind() count, output, weather = data_service.get_total() yearly_outputs = data_service.get_yearly_output() months_temp = data_service.get_months_temp() times_selling = data_service.get_times_selling() months_sun = data_service.get_months_sun() table_list = data_service.get_table_list() return render_template("main.html", month_rain=month_rain, ave_wind=ave_wind, table_list=table_list, count=count, output=output, weather=weather, yearly_outputs=yearly_outputs, months_temp=months_temp, times_selling=times_selling, months_sun=months_sun) # 系统登录路径 @app.route('/login') def login_page(): return render_template("page/login.html") # 系统退出登录路径 @app.route('/logout') def logout_page(): session.clear() return redirect("/login") # 系统注册用户 @app.route('/register', methods=['get']) def register_page(): return render_template("page/register.html") # ----------------------------------------------页面加载模块结束---------------------------------------------- # ----------------------------------------------用户相关模块开始---------------------------------------------- # 用户注册 @app.route('/register', methods=['post']) def register_user(): form = request.form.to_dict() # 获取值 result = user_service.insert_user(form) return result.get() # 用户登录 @app.route('/login', methods=['post']) def login_user(): form = request.form.to_dict() # 获取值 result = user_service.select_user_by_account_password(form) session['user'] = result.data return result.get() # 用户数据分页 @app.route('/page/user/add', methods=['get']) def page_user_add(): return render_template("page/user/add.html") # 用户修改密码 @app.route('/user/reset/password', methods=['post']) def reset_password_user(): form = request.form.to_dict() # 获取值 result = user_service.reset_password(form['old_password'], form['new_password'], form['again_password']) return result.get() @app.route('/add/user', methods=['post']) def add_user(): form = request.form.to_dict() result = user_service.insert_user(form) return result.get() # 用户编辑页面 @app.route('/page/user/edit', methods=['get']) def page_user_edit(): id = request.args.get('id') user = user_service.get_user(id) return render_template("page/user/edit.html", user=user) # 编辑用户接口 @app.route('/edit/user', methods=['post']) def edit_user(): form = request.form.to_dict() result = user_service.edit_user(form) return result.get() # 单个删除用户接口 @app.route('/del/user/<int:id>', methods=['post']) def del_user(id): result = user_service.del_user(id) return result.get() # 批量删除用户接口 @app.route('/del/user', methods=['post']) def del_user_list(): ids = request.args.get('ids') result = user_service.del_user_list(ids) return result.get() # 用户数据分页 @app.route('/list/user', methods=['get']) def user_list(): page = request.args.get('page') limit = request.args.get('limit') where = request.args.get('searchParams') result = user_service.select_user_list(page, limit, where) return result.get() # ----------------------------------------------用户相关模块结束---------------------------------------------- # ----------------------------------------------公告相关模块开始---------------------------------------------- # 公告添加页面 @app.route('/page/notice/add', methods=['get']) def page_notice_add(): return render_template("page/notice/add.html") @app.route('/add/notice', methods=['post']) def add_notice(): form = request.form.to_dict() result = notice_service.insert_notice(form) return result.get() # 数据公告编辑页面 @app.route('/page/notice/edit', methods=['get']) def page_notice_edit(): id = request.args.get('id') notice = notice_service.get_notice(id) return render_template("page/notice/edit.html", notice=notice) # 编辑公告接口 @app.route('/edit/notice', methods=['post']) def edit_notice(): form = request.form.to_dict() result = notice_service.edit_notice(form) return result.get() # 单个删除公告接口 @app.route('/del/notice/<int:id>', methods=['post']) def del_notice(id): result = notice_service.del_notice(id) return result.get() # 批量删除公告接口 @app.route('/del/notice', methods=['post']) def del_notice_list(): ids = request.args.get('ids') result = notice_service.del_notice_list(ids) return result.get() # 公告数据分页 @app.route('/list/notice', methods=['get']) def notice_list(): page = request.args.get('page') limit = request.args.get('limit') where = request.args.get('searchParams') result = notice_service.select_notice_list(page, limit, where) return result.get() # 公告数据最新获取 @app.route('/get/notice/new', methods=['get']) def get_new_notice(): result = notice_service.get_notice_by_new() return result.get() # ----------------------------------------------公告相关模块结束---------------------------------------------- # ----------------------------------------------产量相关模块开始---------------------------------------------- # 产量添加页面跳转 @app.route('/page/output/add', methods=['get']) def page_output_add(): return render_template("page/output/add.html") # 产量新增接口 @app.route('/add/output', methods=['post']) def add_output(): form = request.form.to_dict() result = output_service.insert_output(form) return result.get() # 产量编辑页面跳转 @app.route('/page/output/edit', methods=['get']) def page_output_edit(): id = request.args.get('id') output = output_service.get_output(id) return render_template("page/output/edit.html", output=output) # 产量编辑接口 @app.route('/edit/output', methods=['post']) def edit_output(): form = request.form.to_dict() result = output_service.edit_output(form) return result.get() # 删除单个产量接口 @app.route('/del/output/<int:id>', methods=['post']) def del_output(id): result = output_service.del_output(id) return result.get() # 批量删除产量接口 @app.route('/del/output', methods=['post']) def del_output_list(): ids = request.args.get('ids') result = output_service.del_output_list(ids) return result.get() # 产量数据分页接口 @app.route('/list/output', methods=['get']) def output_list(): page = request.args.get('page') limit = request.args.get('limit') where = request.args.get('searchParams') result = output_service.select_output_list(page, limit, where) return result.get() # ----------------------------------------------产量相关模块结束---------------------------------------------- # ----------------------------------------------气象相关模块开始---------------------------------------------- # 气象添加页面跳转 @app.route('/page/weather/add', methods=['get']) def page_weather_add(): return render_template("page/weather/add.html") # 气象新增接口 @app.route('/add/weather', methods=['post']) def add_weather(): form = request.form.to_dict() result = weather_service.insert_weather(form) return result.get() # 气象编辑页面跳转 @app.route('/page/weather/edit', methods=['get']) def page_weather_edit(): id = request.args.get('id') weather = weather_service.get_weather(id) return render_template("page/weather/edit.html", weather=weather) # 气象编辑接口 @app.route('/edit/weather', methods=['post']) def edit_weather(): form = request.form.to_dict() result = weather_service.edit_weather(form) return result.get() # 删除单个气象接口 @app.route('/del/weather/<int:id>', methods=['post']) def del_weather(id): result = weather_service.del_weather(id) return result.get() # 批量删除气象接口 @app.route('/del/weather', methods=['post']) def del_weather_list(): ids = request.args.get('ids') result = weather_service.del_weather_list(ids) return result.get() # 气象数据分页接口 @app.route('/list/weather', methods=['get']) def weather_list(): page = request.args.get('page') limit = request.args.get('limit') where = request.args.get('searchParams') result = weather_service.select_weather_list(page, limit, where) return result.get() # ----------------------------------------------气象相关模块结束---------------------------------------------- # ----------------------------------------------预测相关模块开始---------------------------------------------- # 预测数据接口 @app.route('/predict/data', methods=['get']) def predict_data(): result = predict_service.get_predict_data() return result.get() # ----------------------------------------------预测相关模块结束---------------------------------------------- if __name__ == '__main__': # 端口号设置 app.run(host="127.0.0.1", port=5000) 续写代码,实现农业数据 农业数据分析可视化大屏 农业产量数据分析
最新发布
12-01
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值