李辉(GreyLi) - Flask 入门教程
使用 Python
和 Flask
开发你的第一个 Web
程序!
记录安装 Flask 需要依赖的相关库及其作用!
# 安装
$ pip install flask
# 依赖
$ pip list --format=columns
Package Version
------------ -------
Flask 1.1.2
click 7.1.2 # 命令行
itsdangerous 1.1.0 # 签名模块
Jinja2 2.11.2 # 模板语言
MarkupSafe 1.1.1 # 模板加速
Werkzeug 1.0.1 # WSGI
剖析示例 Flask 代码的相关参数和对应介绍!
- 第 1-2 行
- 从
flask
包导入Flask
类,通过实例化这个类,创建一个程序对象app
。 - 其中给
Flask
类传入的__name__
变量就是为了查找文件系统上的资源,比如模板目录和静态资源的路径。
- 从
- 第 4-6 行
- 注册一个视图函数,用于处理请求。
- 使用
app.route()
装饰器来为这个函数绑定对应的URL
,当用户在浏览器访问这个URL
的时候,就会触发这个函数,获取返回值并把返回值显示到浏览器窗口。
# app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Welcome to watch list!'
# 默认为production的运行环境
➜ flask run
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [31/Dec/2020 15:42:11] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [31/Dec/2020 15:42:12] "GET /favicon.ico HTTP/1.1" 404 -
Flask 中引入环境变量的原因!
如果我们将上述的示例代码的文件名称修改为其他名称,接着执行 flask run
命令会返回一个错误提示。这是因为 Flask
默认会假设你把程序存储在名为 app.py
或 wsgi.py
的文件中。如果你使用了其他名称,就要设置系统环境变量 FLASK_APP
来告诉 Flask
你要启动哪个程序。这也是为什么 Flask
中存在大量环境变量的原因。
Flask
通过读取这个环境变量值对应的模块寻找要运行的程序实例,你可以把它设置成下面这些值:模块名、Python 导入路径和文件目录路径。
- FLASK_APP
- 用来设置程序运行入口
- FLASK_ENV
- 用来设置程序运行的环境,默认值为
production
环境。 - 如果需要开启调试模式的话,可以将其值设置为
development
即可开启。 - 调试模式中可以显示错误信息,以及在代码发生变动之后,程序会自动重载。
- 用来设置程序运行的环境,默认值为
- python-dotenv
- 通过安装
python-dotenv
专业的工具,来管理系统的环境变量。 - 其中
.env
则用来存储敏感数据,不应该提交进Git
仓库。 - 其中
.flaskenv
用来存储Flask
命令行系统相关的公开环境变量。
- 通过安装
# 安装
$ pip install python-dotenv
介绍视图函数的作用和使用方式!
视图函数的名字是自由定义的,和 URL
规则无关。和定义其他函数或变量一样,只需要让它表达出所要处理页面的含义即可。
除此之外,它还有一个重要的作用:作为代表某个路由的端点(endpoint
),同时用来生成 URL
。对于程序内的 URL
,为了避免手写,Flask
提供了一个 url_for
函数来生成 URL
,它接受的第一个参数就是端点值,默认为视图函数的名称。
类型转换器 | 作用 |
---|---|
缺省 | 字符型;但不能有斜杠 |
path: |
字符型;可有斜杠 |
int: |
整型 |
float: |
浮点型 |
from flask import Flask
from flask.helpers import url_for
app = Flask(__name__)
@app.route('/', methods=['GET'])
def hello():
return 'Welcome to watch list!'
@app.route('/user/<name>')
def user_page(name):
return f'User page: {name}.'
@app.route('/user/<int:user_id>')
def get_user(user_id):
return f'User id: {user_id}'
@app.route('/test')
def test_url_for():
print(url_for('hello')) # /
print(url_for('test_url_for')) # /test
print(url_for('user_page', name='peter')) # /user/peter
print(url_for('test_url_for', num=2)) # /test?num=2
return "Test page."
介绍 Jinja2 模板的简单使用!
Jinja2
的表达式中支持字符串、数值、列表、元祖、字典、布尔值,同时也支持算数运算、比较运算、逻辑运算,还支持过滤器、测试器、函数调用、字符串连接符(~
),等等。
{# ... #}
=> 用来书写注释{{ ... }}
=> 用来标记变量 => 在渲染的时候传递进去{% ... %}
=> 用来标记语句 =>if
语句或for
语句
<h1>{{ username }}的个人主页</h1>
{% if bio %}
<p>{{ bio }}</p>
{% else %}
<p>自我介绍为空。</p>
{% endif %}
- 控制语句
# if
{% if name and name == 'admin' %}
<h1>This is admin console</h1>
{% elif name %}
<h1>Welcome {{ name }}!</h1>
{% else %}
<h1>Please login</h1>
{% endif %}
# for - 有空格
{% for digit in digits %}
{{ digit }}
{% endfor %}
# for - 无空格
{% for digit in digits -%}
{{ digit }}
{%- endfor %}
- 内置变量
变量 | 内容 |
---|---|
loop.index |
循环迭代计数;从 1 开始) |
loop.index0 |
循环迭代计数;从 0 开始) |
loop.revindex |
循环迭代倒序计数;从 len 开始,到 1 结束) |
loop.revindex0 |
循环迭代倒序计数;从 len-1 开始,到 0 结束) |
loop.first |
是否为循环的第一个元素 |
loop.last |
是否为循环的最后一个元素 |
loop.length |
循环序列中元素的个数 |
loop.cycle |
在给定的序列中轮循,如上例在 odd 和 even 两个值间轮循 |
loop.depth |
当前循环在递归中的层级;从 1 开始) |
loop.depth0 |
当前循环在递归中的层级;从 0 开始) |
- 常用语句
# 忽略模板语法
{% raw %}
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% endraw %}
# 忽略自动转义
{% autoescape false %}
<h1>Hello {{ name }}!</h1>
{% endautoescape %}
# 赋值
{% set items = [[1,2],[3,4,5]] %}
# with语句 - 需要安装扩展
{% with foo = 1 %}
{% set bar = 2 %}
{{ foo + bar }}
{% endwith %}
# with语句 - 执行表达式
{% with arr = ['Sunny'] %}
{% do arr.append('Rainy') %}
{{ arr }}
{% endwith %}
介绍模板渲染的基本方式!
按照默认的设置,Flask
会从程序实例(app.py
)所在模块同级目录的 templates
文件夹中寻找模板。
使用 render_template()
函数可以把模板渲染出来,必须传入的参数为模板文件名(相对于 templates
根目录的文件路径),这里即 index.html
文件。为了让模板正确渲染,我们还要把模板内部使用的变量通过关键字参数传入这个函数。render_template()
函数在调用时会识别并执行 index.html
里所有的 Jinja2
语句,返回渲染好的模板内容。在返回的页面中,变量会被替换为实际的值(包括定界符),语句及定界符则会在执行后被移除,同时注释也会一并移除。
from flask import Flask, render_template
from data import movies, name
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html', name=name, movies=movies)
同时,Jinja2
在模板渲染中,还支持多种上下文:
- 请求对象 - request
<p>{{ request.url }}</p>
- 会话对象 - session
<p>User: {{ session.id }}</p>
- 全局对象 - g
<p>DB: {{ g.db }}</p>
- 配置对象 - config
<p>Host: {{ config.DEBUG }}</p>
过滤器包括 Jinja2 库中定义的内置过滤器,当然我们也可以自定义过滤器!
- 自定义过滤器
过滤器简单来讲就是一个函数,在 Flask
应用对象,我们可以使用 add_template_filter
方法来帮我们实现。方法的的第一个参数是过滤器函数,第二个参数是过滤器名称。
from flask import Flask
app = Flask(__name__)
def reverse_order(lst):
return lst[::-1]
# 添加方式一(方法)
app.add_template_filter(reverse_order, 'user_reverse_order')
from flask import Flask
app = Flask(__name__)
# 添加方式二(装饰器)
@app.template_filter('user_reverse_order')
def reverse_order(lst):
return lst[::-1]
Flask
添加过滤器的方法实际上是封装了对 Jinja2
环境变量的操作。在 Flask
应用中,不建议直接访问 Jinja2
的环境变量。如果离开 Flask
环境直接使用 Jinja2
的话,就可以通过 jinja2.Environment
来获取环境变量,并添加过滤器。
app.jinja_env.filters['user_reverse_order'] = reverse_order
考虑到模板代码的重用,Jinja2 提供了块(Block)和宏(Macro)的功能!
- 块 => 功能有些类似于
C
语言中的宏,原理就是代码替换- 模板不支持多继承,也就是子模板中定义的块,不可能同时被两个父模板替换。
- 模板中不能定义多个同名的块,子模板和父模板都不行,因为这样无法知道要替换哪一个部分的内容。
- 可以使用
super()
方法继承父模板中的块里有内容,而不是替换掉
# footer.html
<!DOCTYPE html>
<html lang="en">
<body>
{% block body %}
{% endblock %}
</body>
<footer>
<small>© 2021 <a href="http://movieslist.com">Hello Flask</a></small>
</footer>
</html>
# index.html
{% extends "footer.html" %}
{% block body %}
# {{ super() }}
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello World!</h1>
{% endif %}
{% endblock %}
- 宏 => 功能有些类似于函数,可以传入参数。
- 既然是函数,就有其声明和调用两个部分。
# 声明一个名为input的宏函数
{% macro input(name, type='text', value='') -%}
<input type="{{ type }}" name="{{ name }}" value="{{ value|e }}">
{%- endmacro %}
# 使用调用
<p>{{ input('username', value='user') }}</p>
<p>{{ input('password', 'password') }}</p>
<p>{{ input('submit', 'submit', 'Submit') }}</p>
# 宏的导入
{% from 'form.html' import input %}
资源文件的路径还是依赖 app 初始化的传参来确定的!
静态文件和模板概念恰恰是相反的,其指的是内容不需要动态生成的文件。比如图片、CSS
文件和 JavaScript
脚本等。在 Flask
中,我们需要创建一个 static
文件夹来保存静态文件,它应该和程序模块、templates
文件夹在同一目录层级。
引入这些静态文件需要给出资源所在的 URL
地址,这里我们可以使用 url_for()
这个函数来完成。对于静态文件而言,需要传入的端点值是 static
,同时使用 filename
参数来传入相对于 static
文件夹的文件路径。
在 Python
脚本里,url_for()
函数需要从 flask
包中导入,而在模板中则可以直接使用。这是因为 Flask
把一些常用的函数和对象添加到了模板上下文里,所以不再需要我们再次导入了。
<!-- 引入图片 -->
<img src="{{ url_for('static', filename='foo.jpg') }}" />
<!-- 添加icon -->
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" />
为了方便起见,建议直接使用已经封装好的 CSS 样式工具,比如 BS5 或者 Pure 等!
如果我们引入自己定义的 CSS
样式的话,可以通过 <link>
标签在 HTML
文件中引入对应资源。
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" type="text/css" />
当然,我们这里也可以选择其他的 CSS
样式资源,亦或者是远程的资源文件。
<!-- CSS -->
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1"
crossorigin="anonymous"
/>
<!-- JS -->
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW"
crossorigin="anonymous"
></script>