MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python Web框架Flask深度详解

2023-03-195.0k 阅读

Flask简介

Flask 是一个用 Python 编写的轻量级 Web 框架。它基于 Werkzeug WSGI 工具包和 Jinja2 模板引擎。WSGI(Web Server Gateway Interface)是 Python 应用程序与 Web 服务器之间的标准接口,而 Jinja2 则用于动态生成 HTML 等模板文件。

Flask 之所以被称为轻量级框架,是因为它只提供了构建 Web 应用所需的核心组件,如路由系统、请求处理、响应生成等,开发者可以根据项目需求灵活选择和集成其他扩展库。这使得 Flask 非常适合快速开发小型 Web 应用,同时也能通过各种插件扩展来应对大型项目的复杂需求。

安装Flask

在开始使用 Flask 之前,需要先安装它。如果你已经安装了 Python 和 pip(Python 的包管理工具),可以通过以下命令安装 Flask:

pip install flask

如果使用的是虚拟环境(强烈推荐),在激活虚拟环境后执行上述安装命令。虚拟环境可以隔离不同项目的 Python 依赖,避免版本冲突。例如,使用 venv 创建和激活虚拟环境:

python3 -m venv myenv
source myenv/bin/activate  # Windows 下使用 `myenv\Scripts\activate`

然后再安装 Flask。

第一个Flask应用

创建一个简单的 Flask 应用只需要几行代码。在一个新的 Python 文件(例如 app.py)中编写以下代码:

from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello, World!'


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

在上述代码中:

  1. 首先从 flask 模块导入 Flask 类。
  2. 创建一个 Flask 类的实例 app__name__ 是 Python 的内置变量,它表示当前模块的名称。Flask 使用这个名称来确定应用的根路径等信息。
  3. 使用 @app.route 装饰器定义一个路由。@app.route('/') 表示当用户访问网站根路径(/)时,会执行下面定义的函数 hello_world
  4. hello_world 函数返回一个字符串 'Hello, World!',这个字符串会作为 HTTP 响应返回给客户端。
  5. if __name__ == '__main__': 确保只有在直接运行该脚本时才启动 Flask 应用的开发服务器。app.run() 启动服务器,默认在 127.0.0.1:5000 监听连接。

运行这个脚本:

python app.py

然后在浏览器中访问 http://127.0.0.1:5000/,就能看到 Hello, World! 的页面。

Flask路由系统

基本路由

路由是 Flask 应用中定义 URL 与视图函数之间映射关系的机制。前面的例子展示了一个基本的根路径路由。可以定义更多不同路径的路由,例如:

from flask import Flask

app = Flask(__name__)


@app.route('/about')
def about():
    return 'This is the about page'


@app.route('/contact')
def contact():
    return 'Contact us at contact@example.com'


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

在这个例子中,定义了 /about/contact 两个路由,分别对应 aboutcontact 视图函数。当用户访问 http://127.0.0.1:5000/about 时,会看到 This is the about page,访问 http://127.0.0.1:5000/contact 时,会看到 Contact us at contact@example.com

动态路由

动态路由允许在 URL 中包含变量部分。例如,要创建一个显示用户个人资料的页面,每个用户有不同的 URL,可以这样定义路由:

from flask import Flask

app = Flask(__name__)


@app.route('/user/<username>')
def show_user_profile(username):
    return f'User {username}'


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

@app.route('/user/<username>') 中,<username> 是一个动态部分,它会作为参数传递给 show_user_profile 函数。当访问 http://127.0.0.1:5000/user/john 时,会显示 User john

可以指定动态部分的数据类型,例如:

from flask import Flask

app = Flask(__name__)


@app.route('/post/<int:post_id>')
def show_post(post_id):
    return f'Post ID is {post_id}'


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

这里 post_id 被指定为 int 类型,只有当 URL 中的 post_id 是整数时,这个路由才会匹配。如果访问 http://127.0.0.1:5000/post/123 会正常显示 Post ID is 123,而访问 http://127.0.0.1:5000/post/abc 则会返回 404 错误,因为 abc 不是整数。

路由中的转换器

Flask 内置了几种转换器类型:

  • string:默认类型,接受任何不包含斜杠的文本。
  • int:接受整数。
  • float:接受浮点数。
  • path:类似 string,但可以包含斜杠。
  • uuid:接受 UUID 字符串。

例如,使用 path 转换器:

from flask import Flask

app = Flask(__name__)


@app.route('/file/<path:file_path>')
def show_file(file_path):
    return f'File path is {file_path}'


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

这样,http://127.0.0.1:5000/file/dir1/dir2/file.txt 会被正确匹配,显示 File path is dir1/dir2/file.txt

多个路由装饰器

一个视图函数可以有多个路由装饰器,例如:

from flask import Flask

app = Flask(__name__)


@app.route('/home')
@app.route('/')
def home():
    return 'This is the home page'


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

在这个例子中,/home/ 两个 URL 都映射到 home 视图函数,用户访问这两个路径都会看到 This is the home page

请求处理

获取请求数据

Flask 可以处理各种类型的 HTTP 请求,包括 GET、POST、PUT、DELETE 等。要获取请求中的数据,需要从 flask.request 对象中提取。

对于 GET 请求,数据通常在 URL 的查询字符串中。例如,http://127.0.0.1:5000/search?q=python,可以这样获取 q 参数:

from flask import Flask, request

app = Flask(__name__)


@app.route('/search')
def search():
    query = request.args.get('q')
    return f'Searching for {query}'


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

request.args 是一个类似字典的对象,包含了 URL 查询字符串中的所有参数。get 方法用于获取指定参数的值。

对于 POST 请求,数据通常在请求体中。假设一个 HTML 表单提交数据到 Flask 应用:

<!DOCTYPE html>
<html>

<head>
    <title>Login</title>
</head>

<body>
    <form action="/login" method="post">
        <label for="username">Username:</label><br>
        <input type="text" id="username" name="username"><br>
        <label for="password">Password:</label><br>
        <input type="password" id="password" name="password"><br><br>
        <input type="submit" value="Submit">
    </form>
</body>

</html>

Flask 应用可以这样处理表单数据:

from flask import Flask, request

app = Flask(__name__)


@app.route('/login', methods=['POST'])
def login():
    username = request.form.get('username')
    password = request.form.get('password')
    return f'Username: {username}, Password: {password}'


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

request.form 也是一个类似字典的对象,包含了表单提交的数据。注意,这里路由的 methods 参数指定了该路由只接受 POST 请求。

请求方法

如前面所见,可以通过 methods 参数指定路由接受的请求方法。默认情况下,路由只接受 GET 请求。如果要接受多种请求方法,可以这样写:

from flask import Flask, request

app = Flask(__name__)


@app.route('/example', methods=['GET', 'POST'])
def example():
    if request.method == 'GET':
        return 'This is a GET request'
    elif request.method == 'POST':
        return 'This is a POST request'


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

在这个例子中,/example 路由既接受 GET 请求也接受 POST 请求,并根据不同的请求方法返回不同的内容。

请求头

可以通过 request.headers 获取请求头信息。request.headers 是一个类似字典的对象,包含了所有的请求头字段。例如:

from flask import Flask, request

app = Flask(__name__)


@app.route('/headers')
def headers():
    headers = request.headers
    result = ''
    for key, value in headers.items():
        result += f'{key}: {value}<br>'
    return result


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

访问 /headers 会返回所有请求头信息,格式类似 Host: 127.0.0.1:5000<br>User - Agent: Mozilla/5.0... 等。

响应处理

返回简单响应

前面的例子中,视图函数直接返回一个字符串,这就是一个简单的响应。Flask 会自动将这个字符串包装成一个 HTTP 响应对象,设置合适的状态码(默认 200)和内容类型(text/html; charset=utf-8)。

返回 JSON 响应

在现代 Web 应用中,JSON 是一种常用的数据交换格式。Flask 可以很方便地返回 JSON 响应。首先导入 jsonify 函数:

from flask import Flask, jsonify

app = Flask(__name__)


@app.route('/data')
def get_data():
    data = {'name': 'John', 'age': 30}
    return jsonify(data)


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

jsonify 函数将 Python 字典转换为 JSON 格式的响应,并设置正确的 Content - Type 头为 application/json。访问 /data 会得到 {"name": "John", "age": 30}

设置响应状态码

默认情况下,Flask 视图函数返回的响应状态码是 200。可以通过返回一个包含状态码的元组来设置不同的状态码。例如,返回 404 错误:

from flask import Flask

app = Flask(__name__)


@app.route('/nonexistent')
def nonexistent():
    return 'Not Found', 404


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

这里返回的字符串 Not Found 是响应体,404 是状态码。浏览器访问这个路由会显示 Not Found 并看到 HTTP 404 状态码。

设置响应头

可以通过在返回的元组中添加第三个元素来设置响应头。例如,设置自定义的 X - My - Header 头:

from flask import Flask

app = Flask(__name__)


@app.route('/customheader')
def customheader():
    return 'Custom Header Set', 200, {'X - My - Header': 'Value'}


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

在这个例子中,响应体是 Custom Header Set,状态码是 200,同时设置了 X - My - Header 头的值为 Value

Flask模板

模板基础

Jinja2 是 Flask 默认使用的模板引擎。模板文件通常是 HTML 文件,但可以包含特殊的 Jinja2 语法来动态生成内容。

首先,在项目目录下创建一个 templates 文件夹,Flask 会在这个文件夹中查找模板文件。创建一个简单的模板文件 index.html

<!DOCTYPE html>
<html>

<head>
    <title>Flask Template Example</title>
</head>

<body>
    <h1>Hello, {{ name }}!</h1>
</body>

</html>

在这个模板中,{{ name }} 是一个 Jinja2 变量,它的值会在渲染模板时被替换。

在 Flask 应用中渲染这个模板:

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('index.html', name='John')


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

这里使用 render_template 函数来渲染 index.html 模板,并传递了一个变量 name 其值为 John。访问根路径时,会看到 Hello, John!

模板继承

模板继承允许创建一个基础模板,其他模板可以继承并覆盖其中的部分内容。这有助于保持网站布局的一致性。

创建一个基础模板 base.html

<!DOCTYPE html>
<html>

<head>
    <title>{% block title %}Default Title{% endblock %}</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>

<body>
    <header>
        <h1>My Website</h1>
    </header>
    {% block content %}
    {% endblock %}
    <footer>
        <p>&copy; 2023 My Company</p>
    </footer>
</body>

</html>

在这个基础模板中,{% block title %}{% block content %} 定义了两个可被覆盖的块。{{ url_for('static', filename='styles.css') }} 用于生成静态文件(如 CSS 文件)的 URL。

然后创建一个继承自 base.html 的模板 about.html

{% extends 'base.html' %}

{% block title %}About Us{% endblock %}

{% block content %}
    <p>This is the about page of our company. We are dedicated to providing quality services...</p>
{% endblock %}

在 Flask 应用中渲染 about.html

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/about')
def about():
    return render_template('about.html')


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

访问 /about 时,会看到继承了 base.html 布局,并在相应块中填充了自定义内容的页面。

模板中的控制结构

Jinja2 支持在模板中使用控制结构,如 if 语句和 for 循环。

例如,根据一个条件显示不同的内容:

<!DOCTYPE html>
<html>

<head>
    <title>Conditional Example</title>
</head>

<body>
    {% if user %}
        <h1>Welcome, {{ user }}</h1>
    {% else %}
        <h1>Please login</h1>
    {% endif %}
</body>

</html>

在 Flask 应用中传递 user 变量:

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def index():
    user = 'John'
    return render_template('conditional.html', user=user)


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

会显示 Welcome, John。如果将 user 设置为 None,则会显示 Please login

使用 for 循环遍历列表:

<!DOCTYPE html>
<html>

<head>
    <title>Loop Example</title>
</head>

<body>
    <ul>
        {% for item in items %}
            <li>{{ item }}</li>
        {% endfor %}
    </ul>
</body>

</html>

在 Flask 应用中传递 items 列表:

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def index():
    items = ['Apple', 'Banana', 'Cherry']
    return render_template('loop.html', items=items)


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

会显示一个包含水果名称的无序列表。

Flask扩展

Flask - SQLAlchemy

Flask - SQLAlchemy 是一个用于 Flask 的数据库抽象层扩展,它简化了与各种数据库(如 SQLite、MySQL、PostgreSQL 等)的交互。

首先安装 Flask - SQLAlchemy:

pip install flask - sqlalchemy

然后在 Flask 应用中使用:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] ='sqlite:///test.db'
db = SQLAlchemy(app)


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)


@app.route('/create')
def create():
    db.create_all()
    new_user = User(username='john', email='john@example.com')
    db.session.add(new_user)
    db.session.commit()
    return 'User created'


@app.route('/users')
def users():
    all_users = User.query.all()
    result = ''
    for user in all_users:
        result += f'ID: {user.id}, Username: {user.username}, Email: {user.email}<br>'
    return result


if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run()

在这个例子中:

  1. 配置了 SQLite 数据库,SQLALCHEMY_DATABASE_URI 定义了数据库的位置。
  2. 创建了一个 User 模型类,它继承自 db.Modelidusernameemail 是模型的属性,通过 db.Column 定义,指定了数据类型、主键、唯一性等约束。
  3. create 视图函数创建数据库表(如果不存在),并添加一个新用户。
  4. users 视图函数查询所有用户,并返回用户信息。

Flask - Bootstrap

Flask - Bootstrap 集成了 Bootstrap 前端框架到 Flask 应用中。Bootstrap 提供了丰富的 CSS 和 JavaScript 组件,帮助快速构建美观的响应式界面。

安装 Flask - Bootstrap:

pip install flask - bootstrap

在 Flask 应用中初始化:

from flask import Flask
from flask_bootstrap import Bootstrap

app = Flask(__name__)
Bootstrap(app)


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


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

在模板文件 index.html 中使用 Bootstrap 组件,例如:

{% extends 'bootstrap/base.html' %}

{% block title %}Flask Bootstrap Example{% endblock %}

{% block content %}
    <div class="container">
        <h1>Welcome to my site</h1>
        <p class="lead">This is a simple example using Flask - Bootstrap.</p>
        <button type="button" class="btn btn-primary">Click me</button>
    </div>
{% endblock %}

这里继承了 Flask - Bootstrap 提供的 bootstrap/base.html 模板,使用了 Bootstrap 的 CSS 类来样式化内容和添加按钮。

Flask - Login

Flask - Login 用于处理用户认证和会话管理。它提供了用户登录、注销、记住用户状态等功能。

安装 Flask - Login:

pip install flask - login

在 Flask 应用中使用:

from flask import Flask, render_template, request, redirect, url_for
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required

app = Flask(__name__)
app.secret_key = 'your_secret_key'

login_manager = LoginManager()
login_manager.init_app(app)


class User(UserMixin):
    def __init__(self, id):
        self.id = id


# 模拟用户数据库
users = {'john': {'password': 'password123'}}


@login_manager.user_loader
def load_user(user_id):
    return User(user_id)


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        if username in users and users[username]['password'] == password:
            user = User(username)
            login_user(user)
            return redirect(url_for('protected'))
        else:
            return 'Invalid credentials'
    return render_template('login.html')


@app.route('/protected')
@login_required
def protected():
    return 'This is a protected page'


@app.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('login'))


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

在这个例子中:

  1. 初始化了 LoginManager 并设置了应用的密钥。
  2. 创建了一个简单的 User 类,继承自 UserMixin,它提供了 Flask - Login 所需的一些默认方法。
  3. load_user 函数是 Flask - Login 要求的,用于从用户 ID 加载用户对象。
  4. login 视图函数处理用户登录逻辑,验证用户名和密码后使用 login_user 登录用户。
  5. protected 视图函数使用 @login_required 装饰器,确保只有登录用户才能访问。
  6. logout 视图函数使用 logout_user 注销用户。

Flask部署

开发服务器与生产服务器

在开发过程中,使用 app.run() 启动的是 Flask 的开发服务器。它具有自动重载功能,方便开发调试,但不适合生产环境。

在生产环境中,需要使用更强大的 Web 服务器,如 Gunicorn 和 Nginx。

使用Gunicorn部署

Gunicorn 是一个基于 WSGI 的 HTTP 服务器。首先安装 Gunicorn:

pip install gunicorn

假设 Flask 应用入口文件是 app.py,应用实例是 app,可以通过以下命令启动 Gunicorn:

gunicorn -w 4 -b 127.0.0.1:8000 app:app

这里 -w 4 表示使用 4 个工作进程,-b 127.0.0.1:8000 表示绑定到 127.0.0.1:8000 地址,app:app 表示从 app.py 文件中导入 app 实例。

使用Nginx作为反向代理

Nginx 可以作为反向代理服务器,将请求转发给 Gunicorn 运行的 Flask 应用。

首先安装 Nginx(不同操作系统安装方法不同)。然后配置 Nginx,在 Nginx 的配置目录(如 /etc/nginx/sites - available)中创建一个新的配置文件(例如 flaskapp):

server {
    listen 80;
    server_name your_domain.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X - Real - IP $remote_addr;
        proxy_set_header X - Forwarded - For $proxy_add_x_forwarded_for;
        proxy_set_header X - Forwarded - Proto $scheme;
    }
}

上述配置中,listen 80 表示监听 80 端口,server_name 是你的域名。location / 块将所有请求转发到 http://127.0.0.1:8000,即 Gunicorn 运行的地址,并设置了一些代理头信息。

然后创建一个符号链接将配置文件链接到 sites - enabled 目录:

ln -s /etc/nginx/sites - available/flaskapp /etc/nginx/sites - enabled/

最后重启 Nginx 使配置生效:

sudo systemctl restart nginx

这样,通过域名访问时,Nginx 会将请求转发给 Gunicorn 运行的 Flask 应用。

通过以上对 Flask 的深度详解,涵盖了从基础的应用创建、路由系统、请求响应处理,到模板使用、扩展集成以及部署等方面,相信你对 Flask 框架有了全面深入的了解,可以基于 Flask 开发出各种功能丰富的 Web 应用。