0. Flask 初识

Flask 初识

django是个大而全的框架,flask是一个轻量级的框架

django内部为我们提供了非常多的组件,orm/session/cookie/admin/modelform/form/路由/视图/模版/中间件/分页/auth/contenttype/缓存/信号/多数据库连接

flask框架本身没有太多的功能,路由/视图/模版(jinja2)/session/中间件,第三方组件非常齐全

注意事项:

  • django的请求处理是逐一封装和传递的
  • flask的请求是利用上下文管理来实现的

flask本身是没有WSGI,是依赖于第三方的werkzeug实现的,重要的两个一个是werkzeug(WSGI)、一个是jinja2(模版渲染)

Flask 目录

  • flask目录结构

    __init__.py :Flask框架的初始模块,创建Flask应用程序对象的入口。

    app.py:定义了Flask应用的核心类,包括请求处理、URL路由、视图函数等。

    globals.py:定义了一些全局变量和常量。

    cli.py:定义了命令行接口相关的功能,如启动服务器等。blueprints.py:实现了蓝图(Blueprint)功能,用于组织和管理应用的模块化组件。

    request.py:封装了请求对象,提供了方便的访问请求数据的方法。

    session.py:封装了会话(Session)功能,用于在客户端和服务器之间存储和传递数据。

    template.py:模板引擎相关的代码,用于渲染生成HTML页面。

    views.py:定义了基于类的视图的实现。

    helpers.py:定义了各种辅助函数,如URL生成、重定向等。

  • flask文件依赖关系

    __init__.py是Flask的入口模块,它导入了其他核心模块和各种类、函数以及对象。

    app.py依赖于init.py和其他模块,它使用了其中定义的类和函数来实现请求处理、路由等功能。

    globals.py中定义的全局变量和常量可以被其他模块导入和使用。

    cli.py依赖于init.py和其他模块,它通过命令行接口来管理应用程序的各种操作和配置。

    blueprints.py提供了蓝图功能,其他模块可以导入该模块,并使用其中定义的类和函数来创建和注册蓝图。

    request.py封装了请求对象相关的功能,其他模块可以导入其中的类和函数来处理请求数据。

    session.py提供了会话功能,其他模块可以导入其中的类和函数来进行会话管理。

    template.py实现了模板引擎功能,其他模块可以导入其中的类和函数来渲染生成HTML页面。

    views.py实现了基于类的视图,其他模块可以导入其中的类和函数来定义和处理视图。

    helpers.py提供了各种辅助函数,其他模块可以导入其中的函数来进行操作和功能扩展。

Flask中的werkzeug

Werkzeug是一个Python库,用于开发Web应用程序。它是一个WSGI(Web Server Gateway Interface)工具包,提供了一系列实用功能来帮助开发者处理HTTP请求、响应、URLs等等。Werkzeug的设计非常灵活,可以用作构建各种Web框架的基础。

Werkzeug的特性包括:

  • 请求和响应对象:Werkzeug为HTTP请求和响应提供了易于使用的包装器,使得开发者可以更方便地处理这些请求和响应。
  • URL路由:Werkzeug提供了强大的URL路由功能,能够帮助开发者将URL映射到相应的处理函数。(文末进行举例说明)
  • 错误处理:Werkzeug提供了异常处理机制,可以方便地处理HTTP错误,并且提供了一个交互式的调试器,使得在开发过程中调试错误更加方便。
  • HTTP工具:Werkzeug还提供了一些其他的HTTP相关的工具,比如处理cookies、文件上传等。
# django和flask内部都没有实现socket,而是wsgi实现。
# wsgi是web服务网管接口,他是一个协议,实现它的协议的有:wsgiref/werkzurg/uwsgi
# django之前
from wsgiref.simple_server import make_server

def run(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [bytes('<h1>Hello, web!</h1>', encoding='utf-8'), ]

if __name__ == '__main__':
    httpd = make_server('127.0.0.1', 8000, run)
    httpd.serve_forever()
# Flask之前
from werkzeug.serving import run_simple
from werkzeug.wrappers import Response

def func(environ,start_response):
    response = Response('您好!')
    print("请求来了")
    return response(environ, start_response)

if __name__ == '__main__':
    run_simple("127.0.0.1",5000,func)
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
127.0.0.1 - - [15/May/2024 10:34:43] "GET / HTTP/1.1" 200 -

请求来了
from werkzeug.serving import run_simple

class Flask(object):
    def __call__(self, environ, start_response):
        # 返回XXX类对象(执行flask源码功能)
        return 'XXX'

app = Flask()

if __name__ == '__main__':
    # 将一个实例像函数一样调用,需要在类中实现__call__方法
    run_simple("127.0.0.1", 5000, app)
from werkzeug.serving import run_simple

class Flask(object):
    def __call__(self,environ,start_response):
        # 返回XXX类对象
        return 'XXX'

    def run(self):
        # 将一个实例像函数一样调用,需要在类中实现__call__方法
        run_simple("127.0.0.1",5000,self)

app = Flask()

if __name__ == '__main__':
    app.run()
# 快速Flask
from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
    return "hello world"

if __name__ == '__main__':
    app.run()
from werkzeug.wrappers import Request, Response

def application(environ, start_response):
# 这定义了一个函数,名为application。这个函数符合WSGI的规范,是一个典型的WSGI应用。
# 它接受两个参数,environ是一个包含所有HTTP请求信息的字典,start_response是一个发送HTTP响应的回调函数。
    request = Request(environ)
    text = 'Hello, %s!' % request.args.get('name', 'World')
    response = Response(text, mimetype='text/plain')
    return response(environ, start_response)

if __name__ == '__main__':
    from werkzeug.serving import run_simple
    run_simple('localhost', 4000, application)
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://localhost:4000
Press CTRL+C to quit
127.0.0.1 - - [13/May/2024 11:32:13] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2024 11:32:13] "GET /favicon.ico HTTP/1.1" 200 -
# werkzeug的路由系统
from werkzeug.routing import Map, Rule
from werkzeug.wrappers import Request, Response

# 创建一个 URL 映射
url_map = Map([
    Rule('/', endpoint='hello'),  # 将根路径 / 映射到 'hello' 端点
    Rule('/bye', endpoint='bye')  # 将 /bye 路径映射到 'bye' 端点
])

# 创建处理函数字典
view_functions = {
    'hello': lambda: Response('Hello, World!'),
    'bye': lambda: Response('Goodbye, World!')
}

# 创建 WSGI 应用
def application(environ, start_response):
    request = Request(environ)
    urls = url_map.bind_to_environ(request.environ)  # 将环境绑定到 URL 映射
    endpoint, args = urls.match()  # 从 URL 映射中匹配请求路径
    response = view_functions[endpoint]()  # 使用相应的处理函数处理请求
    return response(environ, start_response)

# 在主程序中运行 WSGI 服务器
if __name__ == '__main__':
    from werkzeug.serving import run_simple
    run_simple('localhost', 4000, application)

总结:

flask框架是基于werkzeug的wsgi实现的,flask自己没有wsgi

用户请求一但到来,就会调用app.call方法

写flask的标准流程:

  • 创建Flask对象
  • 路由和视图函数
  • app.run() 运行

Flask 示例

登录示例

from flask import Flask,render_template,request,redirect,jsonify

app = Flask(__name__)

@app.route("/login",methods=['GET',"POST"])
def login():
    if request.method == "GET":
        return render_template("login.html")
    user = request.form.get("user")
    pwd = request.form.get("pwd")
    if user == "tang" and pwd == "123456":
        return redirect("/index")
    error_msg = "用户名或者密码错误"
    # return render_template("login.html",**{"error": error_msg})
    return render_template("login.html",error=error_msg)

@app.route("/")
def first():
    return jsonify({"code":1000,"data":"index"})

@app.route("/index")
def index():
    return "首页"

if __name__ == '__main__':
    app.run()
* Serving Flask app '__main__'
 * Debug mode: off

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
127.0.0.1 - - [13/May/2024 13:38:13] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2024 13:38:16] "GET /login HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2024 13:38:22] "POST /login HTTP/1.1" 302 -
127.0.0.1 - - [13/May/2024 13:38:22] "GET /index HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2024 13:38:30] "POST /login HTTP/1.1" 200 -
<!-- Flask\templates\login.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Flask login 示例</title>
</head>
<body>
  <h1>用户登录界面</h1>
  <form method="post">
      <input type="text" name="user"></input>
      <input type="text" name="pwd"></input>
      <input type="submit" value="提交"> <span style="color: red">{{error}}</span>
  </form>
</body>
</html>

页面数据编辑

获取查询字符串的两种方式:

  • 第一种使用 request.args.get 去获得
    • nid = request.args.get("nid")
  • 第二种使用\<int:nid> 加上handler传参的方式去获得,并将参数转化成int类型,不加就是字符串类型
    • @app.route("/del/\<int:nid>")

url的跳转方式有两种:

@app.route("index",endpoint="idx")
def index():
    pass

def a():
    # 直接使用redirect + 路由的方式跳转
    return redirct("/index")

def b():
    # 使用redirect + url_for + endpoint别名的方式进行跳转
    return rediect(url_for("idx"))
from flask import Flask,render_template,request,redirect,url_for

app = Flask(__name__)

data_dict = {
    "1":{"name":"狗剩",'age':18,"gender":"男"},
    "2":{"name":"钢蛋",'age':20,"gender":"女"},
    "3":{"name":"铁锤",'age':40}
}

@app.route("/index",endpoint='idx')
def index():
    return render_template("index.html",data_dict=data_dict)

@app.route("/edit",methods=["GET","POST"])
def edit():
    # 拿到的都是字符串类型
    nid = request.args.get("nid")

    if request.method =="GET":
        info = data_dict[nid]
        return render_template("edit.html",info=info)
    user = request.form.get('name')
    age = int(request.form.get('age'))
    gender = request.form.get("gender")
    data_dict[nid]["name"] = user
    data_dict[nid]["age"] = age
    data_dict[nid]["gender"] = gender
    return redirect(url_for("idx"))

@app.route("/del/<nid>")
def delete(nid):
    del data_dict[nid]
    print("删除成功")
    return redirect(url_for('idx'))

if __name__ == '__main__':
    app.run()
总结:
  • flask的路由使用的就是装饰器
  • 路由参数 路径参数、endpoint、methods
    • endpoint不能重名、endpoint不写默认和handler同名
  • 使用动态路由
  • 提交数据
    • request.args
    • request.form
  • 返回数据
    • render_template("模板文件")
    • render.jsonify()
    • render.redirect('/index') render.redirect(url_for('idx'))
    • return "xxxxx"
  • 模板处理
    • {{ name }}
  {% for key,value in term.items() %}
      {{ key }}
      {{ value }}
  {% endfor %}
  • 在jinja2中使用注释,不能直接使用html的!的方式
    <!-- <td>{{  key  }}</td> -->    # 不会生效
    {# <td>{{  key  }}</td> #}    # 生效

functools改变装饰器指向

# 使用functools改变装饰器指向
def auth(func):
    def inner(*args,**kwargs):
        return func(*args,**kwargs)
    return inner

@auth
def login():
    pass

print(login.__name__)

"""
inner
"""
inner

'\ninner\n'
# 可以使用functools改变装饰器指向
import functools
def auth(func):
    @functools.wraps(func)
    def inner(*args,**kwargs):
        return func(*args,**kwargs)
    return inner

@auth
def login():
    pass

print(login.__name__)
"""
login
"""
login

'\nlogin\n'
# 多个装饰器的执行顺序
# 离函数最近的装饰器最先执行,由内向外
import functools
def auth1(func):
    # print("auth1")
    @functools.wraps(func)
    def inner(*args,**kwargs):
        return func(*args,**kwargs)
    return inner

def auth0(func):
    # print("auth0")
    @functools.wraps(func)
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    return inner

@auth0
@auth1
def login():
    pass

login()
"""
auth1
auth0
"""

# 实际上被包裹上之后,执行的函数变成了inner,但是名字还是login
'\nauth1\nauth0\n'

保存用户的登录信息

flask的session信息在服务端不存储,而是通过加密的方式放在浏览器段进行存储

# 请求任意一个接口都需要session的状态
from flask import Flask, render_template, request, session, redirect, url_for
import functools

app = Flask(__name__)
app.secret_key = "013uherwengnkwdgnkwn"

def auth(func):
    @functools.wraps(func)
    def inner(*args, **kwargs):
        username = session.get('username')
        if not username:
            return redirect(url_for('login'))   # 没定义endpoing ,默认是login()函数名
        return func(*args, **kwargs)
    return inner

@app.route("/login", methods=['GET', "POST"])
def login():
    if request.method == "GET":
        return render_template("login.html")
    user = request.form.get("user")
    pwd = request.form.get("pwd")
    if user == "tang" and pwd == "123456":
        session['username'] = 'tang'
        return redirect("/index")
    error_msg = "用户名或者密码错误"
    return render_template("login.html", error=error_msg)

@app.route("/edit")
@auth
def edit():
    return 'edit'

@app.route("/del")
@auth
def delete():
    return "删除"

@app.route("/index")
@auth
def index():
    return "首页"

if __name__ == '__main__':
    app.run()

蓝图 (blue print)

帮助我们构建一个业务功能可拆分的目录结构

  • 项目目录
    • 项目文件
    • templates
    • static
    • views(视图)
      • killer.py
      • waws.py
    • __init__.py
    • manage.py (启动文件)
# cd flask_project_demo  项目目录
# flask_project_demo/flask_project/__init__.py
from flask import Flask
from flask_project_demo.flask_project.views.v_killer import killer
from flask_project_demo.flask_project.views.v_waws import waws

def create_app():
    app = Flask(__name__)
    app.secret_key = "jdoasfajkfpafkadsf"

    @app.route('/index')
    def index():
        return 'index'

    # 注册蓝图
    # 请求加上前缀 http://127.0.0.1:5000/killer/f2
    app.register_blueprint(killer,url_prefix='/killer')
    app.register_blueprint(waws,url_prefix='/waws')

    return app

# flask_project_demo/manage.py
from Flask.flask_project_demo.flask_project import create_app

app = create_app()

if __name__ == '__main__':
    app.run()

# flask_project_demo/flask_project/views/v_killer.py
from flask import Blueprint

killer = Blueprint('v_killer',__name__)

@killer.route('/f1')
def f1():
    return 'killer-f1'

@killer.route('/f2')
def f2():
    return 'killer-f2'

# flask_project_demo/flask_project/views/v_waws.py
from flask import Blueprint

waws = Blueprint('v_waws',__name__)

@waws.route('/f3')
def f1():
    return 'waws-f3'

@waws.route('/f4')
def f2():
    return 'waws-f4'