2. 模板引擎基础:Jinja2 语法入门
Jinja2 是 Python 生态中最流行的模板引擎,被 Flask、Django(自 1.8 起内置)、Sanic 等框架广泛采用。它通过 模板 + 上下文数据 的分离机制,将业务逻辑与展示逻辑解耦。本章聚焦 Jinja2 的核心语法:变量插值、过滤器链式处理、控制结构,为后续“模板渲染性能优化”和“异步导出”打下基础。
2.1 变量与表达式
Jinja2 使用 {{ ... }} 作为变量输出标记,支持 Python 基本类型、列表、字典、对象属性访问。
| 语法 | 示例 | 输出结果 |
|---|---|---|
{{ var }} |
{{ name }} |
Alice |
{{ list[idx] }} |
{{ users[0] }} |
Alice |
{{ dict.key }} |
{{ config.host }} |
127.0.0.1 |
{{ obj.attr }} |
{{ user.name }} |
Alice |
{{ 'string' }} |
{{ 'Hello' }} |
Hello |
避坑提示: 当变量不存在时,Jinja2 默认输出空字符串(
''),不会抛出异常。若需严格校验,可在渲染时设置 undefined=StrictUndefined。
2.2 过滤器:数据变换的管道
过滤器通过 | 符号连接,支持链式调用。常用过滤器如下:
| 过滤器 | 作用 | 示例 | 输出 |
|---|---|---|---|
safe |
标记为安全 HTML,不转义 | {{ '<b>Bold</b>' | safe }} |
Bold |
escape / e |
HTML 转义 | {{ '<b>Bold</b>' | e }} |
<b>Bold</b> |
default |
变量为空时使用默认值 | {{ nickname | default('匿名') }} |
匿名 |
length |
返回序列长度 | {{ items | length }} |
5 |
join |
列表拼接为字符串 | {{ tags | join(', ') }} |
python, flask, jinja2 |
upper / lower |
大小写转换 | {{ 'hello' | upper }} |
HELLO |
int / float |
类型转换 | {{ '3.14' | float }} |
3.14 |
# 链式过滤器示例
{{ " Hello World " | trim | upper | replace(" ", "_") }}
# 输出: HELLO_WORLD
2.3 控制结构:条件与循环
控制结构使用 {% ... %} 标记,必须配合 {% endif %}、{% endfor %} 等结束标签。
2.3.1 条件判断
{% if user.is_admin %}
<span class="badge badge-admin">管理员</span>
{% elif user.is_editor %}
<span class="badge badge-editor">编辑</span>
{% else %}
<span class="badge badge-user">普通用户</span>
{% endif %}
2.3.2 循环遍历
<ul>
{% for item in items %}
<li>{{ loop.index }}. {{ item.name }} - {{ item.price | int }} 元</li>
{% else %}
<li>暂无数据</li>
{% endfor %}
</ul>
loop 是 Jinja2 内置的循环辅助对象,常用属性:
| 属性 | 说明 | 示例值 |
|---|---|---|
loop.index |
当前迭代次数(从 1 开始) | 1, 2, 3 |
loop.index0 |
当前迭代次数(从 0 开始) | 0, 1, 2 |
loop.first |
是否为第一个元素 | True / False |
loop.last |
是否为最后一个元素 | True / False |
loop.length |
序列总长度 | 10 |
2.4 模板继承与宏
Jinja2 通过 {% extends %} 和 {% block %} 实现模板复用,通过 {% macro %} 定义可复用的函数式片段。
<!-- base.html -->
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}默认标题{% endblock %}</title>
</head>
<body>
<header>{% block header %}通用头部{% endblock %}</header>
<main>{% block content %}{% endblock %}</main>
<footer>{% block footer %}通用底部{% endblock %}</footer>
</body>
</html>
<!-- page.html -->
{% extends "base.html" %}
{% block title %}用户列表{% endblock %}
{% block content %}
<h1>用户管理</h1>
{% include "_user_table.html" %}
{% endblock %}
性能要点: 模板继承在编译阶段会合并为单一模板,不会产生多次文件 IO。但
{% include %} 每次渲染都会重新加载子模板,在热点路径上应避免过多嵌套 include。
2.5 Jinja2 渲染流程与性能关键点
下图展示了 Jinja2 从模板源码到最终 HTML 输出的完整生命周期,其中 编译缓存 和 字节码复用 是影响渲染性能的核心环节。
2.6 实战:渲染一个用户列表
结合变量、过滤器、循环和条件判断,实现一个带分页状态的用户表格:
from jinja2 import Environment, FileSystemLoader
env = Environment(
loader=FileSystemLoader('templates/'),
autoescape=True,
trim_blocks=True,
lstrip_blocks=True
)
template = env.get_template('user_list.html')
context = {
'users': [
{'name': 'Alice', 'role': 'admin', 'active': True},
{'name': 'Bob', 'role': 'editor', 'active': False},
{'name': 'Charlie', 'role': 'user', 'active': True},
],
'page': 1,
'total_pages': 5
}
output = template.render(context)
print(output)
<!-- user_list.html -->
<table>
<thead>
<tr>
<th>#</th>
<th>用户名</th>
<th>角色</th>
<th>状态</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr class="{{ 'active' if user.active else 'inactive' }}">
<td>{{ loop.index }}</td>
<td>{{ user.name | e }}</td>
<td>{{ user.role | capitalize }}</td>
<td>{{ '在线' if user.active else '离线' }}</td>
</tr>
{% else %}
<tr><td colspan="4">暂无用户数据</td></tr>
{% endfor %}
</tbody>
</table>
<p>第 {{ page }} / {{ total_pages }} 页</p>
安全警告: 默认情况下 Jinja2 会对所有变量进行 HTML 转义(
autoescape=True)。如果渲染用户提交的富文本内容,务必使用 bleach 或 nh3 库进行白名单过滤,切勿直接使用 | safe 过滤器。
2.7 本章小结
- 变量插值:
{{ }}支持点号、下标、方法调用,注意Undefined行为。 - 过滤器:链式管道处理数据,
safe、default、join是高频使用项。 - 控制结构:
{% if %}/{% for %}配合loop对象实现复杂逻辑。 - 继承与宏:
{% extends %}+{% block %}实现布局复用,{% macro %}封装可复用片段。 - 性能:编译缓存是渲染性能的基石,后续章节将结合 Redis 实现分布式模板缓存。