3. Jinja2模板引擎:模板基础、变量与表达式、控制结构(if/for)、模板继承
好,咱们今天聊聊Jinja2。说实话,这是Flask里我最喜欢的部分之一。为什么?因为它让后端开发变得特别有「手感」。你想想看,以前写后端返回HTML,要么拼字符串,要么用笨重的模板。Jinja2的出现,说白了就是让Python代码和HTML页面能优雅地「对话」。
3.1 模板基础:从零开始
先说说模板是什么。模板就是一个普通的HTML文件,只不过里面加了点「料」——Jinja2的语法标记。Flask默认会在项目的 templates/ 文件夹里找模板文件。
我个人习惯的项目结构是这样的:
myapp/
├── app.py
└── templates/
├── index.html
└── user.html
在 app.py 里,用 render_template() 来渲染模板:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
嗯,这里要注意:render_template() 会自动去 templates/ 目录下找文件。我曾经见过有人把模板放在根目录,结果死活渲染不出来——其实就是路径不对。
3.2 变量与表达式:让模板「活」起来
模板里怎么传数据?用 {{ }} 双花括号。这是Jinja2的变量输出语法。
看个例子:
# app.py
@app.route('/user/<name>')
def user(name):
return render_template('user.html', username=name, age=28)
<!-- user.html -->
<h1>欢迎你,{{ username }}!</h1>
<p>你的年龄是:{{ age }} 岁</p>
变量可以是任意Python对象。字典、列表、甚至自定义对象都行。访问属性用点号 .,访问字典键用中括号 [] 或者点号都行。
表达式也支持:
{{ 1 + 2 }}→ 输出 3{{ "Hello " ~ name }}→ 字符串拼接{{ name|upper }}→ 过滤器,把name转大写{{ list|length }}→ 获取列表长度
过滤器是Jinja2的一大亮点。我项目中用得最多的过滤器是 safe。为什么?因为Jinja2默认会转义HTML,防止XSS攻击。但有时候你确实想输出HTML标签,这时候就要用 |safe。
小技巧:如果你确定内容安全,可以用 {{ content|safe }}。但千万别对用户输入的内容用safe,那是给自己挖坑。
3.3 控制结构:if/for 让模板有逻辑
模板里也能写逻辑?当然可以。用 {% %} 包裹控制语句。
3.3.1 if 判断
<!-- 根据用户状态显示不同内容 -->
{% if user.is_admin %}
<p>管理员,你好!</p>
{% elif user.is_vip %}
<p>尊贵的VIP用户,欢迎回来!</p>
{% else %}
<p>普通用户,登录成功。</p>
{% endif %}
我在项目中遇到过这样一个坑:if 判断里,空列表、空字符串、None、0 都会被当作 False。所以 {% if list %} 这种写法是安全的,不用显式判断长度。
3.3.2 for 循环
遍历列表是最常见的场景:
<ul>
{% for item in items %}
<li>{{ item.name }} - {{ item.price }} 元</li>
{% endfor %}
</ul>
Jinja2的for循环还内置了一些特殊变量,我经常用:
| 变量 | 说明 |
|---|---|
loop.index |
当前循环次数,从1开始 |
loop.index0 |
当前循环次数,从0开始 |
loop.first |
是否是第一次循环 |
loop.last |
是否是最后一次循环 |
loop.length |
循环的总长度 |
举个例子,给表格的奇偶行加不同背景色:
<table>
{% for row in rows %}
<tr class="{{ 'even' if loop.index is even else 'odd' }}">
<td>{{ loop.index }}. {{ row.title }}</td>
</tr>
{% endfor %}
</table>
注意:Jinja2的for循环不支持 break 和 continue。如果你需要提前终止循环,应该在Python视图函数里处理好数据再传给模板。我曾经试图在模板里用 break,结果直接报错——嗯,这个坑我替你们踩过了。
3.4 模板继承:告别重复代码
这是Jinja2最强大的功能,没有之一。你想想看,一个网站通常有统一的头部、导航、底部。如果每个页面都写一遍,那维护起来简直是噩梦。
模板继承的思路很简单:
- 写一个「基模板」(base.html),定义整体框架
- 在需要填充的地方留出「块」(block)
- 子模板继承基模板,只填充自己特有的内容
先看基模板:
<!-- base.html -->
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}默认标题{% endblock %}</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<header>
<h1>我的网站</h1>
<nav>...</nav>
</header>
<main>
{% block content %}
<!-- 子模板会填充这里 -->
{% endblock %}
</main>
<footer>
<p>版权所有 © 2024</p>
</footer>
</body>
</html>
然后子模板继承它:
<!-- about.html -->
{% extends "base.html" %}
{% block title %}关于我们{% endblock %}
{% block content %}
<h2>关于本站</h2>
<p>这是一个用Flask构建的示例网站。</p>
{% endblock %}
渲染 about.html 时,Jinja2会自动把 block 里的内容填充到基模板的对应位置。头部、导航、底部都不用重复写。
继承的几点经验:
- 子模板里只能定义
block,不能有block之外的HTML内容(会被忽略) - 可以用
{{ super() }}在子模板里引用父模板block的内容 - 支持多层继承:A继承B,B继承C,但别超过三层,否则维护起来很痛苦
我曾经接手过一个项目,模板继承链长达五层。每次改个样式都要翻半天文件,最后我重构成了两层——基模板和页面模板。说白了,继承是为了简化,不是为了炫技。
3.5 实战小技巧
最后分享几个我常用的技巧:
- 宏(macro):类似函数,可以复用HTML片段。比如渲染表单字段,定义一个宏,到处调用。
- include:直接引入另一个模板文件。适合复用独立的组件,比如
{% include 'header.html' %}。 - set 赋值:在模板里定义变量,
{% set title = "首页" %},适合临时存储数据。
嗯,Jinja2的内容就这些。说白了,它就是让HTML模板变得像Python一样灵活。你掌握了变量、控制结构、继承这三个核心,基本就能应付90%的场景了。剩下的,遇到问题查文档就行。