一、什么是 Jinja2?
Jinja2 是 Python 生态中最流行的模板引擎之一,由 Flask 框架的作者 Armin Ronacher 开发。它允许你在 HTML、XML、Markdown 等文本文件中嵌入动态内容,实现数据与展示分离。
核心特性
- 沙箱执行 — 模板中不能随意执行任意 Python 代码,安全可控
- 模板继承 — 通过
extends和block实现布局复用 - 强大的过滤器系统 — 对变量进行格式化、截断、转义等处理
- 自动 HTML 转义 — 默认开启,防止 XSS 攻击
- 高性能 — 编译成 Python 字节码缓存执行
- 可扩展 — 自定义过滤器、测试器、全局函数
你正在阅读的这个网站就是基于 Jinja2 搭建的——本站的导航栏、页脚、文章列表、工具页全部通过 Jinja2 模板渲染生成。
二、安装
Jinja2 是独立库,不依赖 Web 框架:
pip install Jinja2如果你用 Flask,Jinja2 已经是内置依赖,无需单独安装:
pip install flask # 自带 Jinja2验证安装:
import jinja2
print(jinja2.__version__) # 3.1.x三、快速上手
3.1 最简单的示例
from jinja2 import Environment, BaseLoader
# 创建环境
env = Environment()
# 渲染模板
template = env.from_string("Hello, {{ name }}!")
result = template.render(name="World")
print(result) # Hello, World!3.2 从文件加载模板
from jinja2 import Environment, FileSystemLoader
# 指定模板目录
env = Environment(
loader=FileSystemLoader("templates/"),
autoescape=True # 自动 HTML 转义
)
# 渲染
template = env.get_template("index.html")
html = template.render(title="首页", users=["Alice", "Bob"])目录结构:
project/
├── templates/
│ ├── base.html # 基础布局
│ └── index.html # 首页
└── app.py # Python 脚本四、模板语法
Jinja2 使用三种定界符:
| 定界符 | 用途 | 示例 |
|---|---|---|
{{ ... }} |
输出变量/表达式 | {{ user.name }} |
{% ... %} |
控制结构(判断、循环等) | {% if user %} |
{# ... #} |
注释(不出现在输出中) | {# 这是注释 #} |
4.1 变量输出
<p>{{ username }}</p>
<p>{{ article.title }}</p>
<p>{{ articles[0].title }}</p>
<p>{{ dict_value["key"] }}</p>4.2 过滤器
过滤器用管道符 | 连接,可以对变量进行各种处理:
<!-- 字符串大写 -->
{{ "hello"|upper }} <!-- HELLO -->
<!-- 首字母大写 -->
{{ "hello world"|title }} <!-- Hello World -->
<!-- 默认值(变量为 None 或未定义时使用) -->
{{ undefined_var|default("默认值") }}
<!-- 截断 -->
{{ long_text|truncate(50) }} <!-- 截取前50字符 -->
<!-- JSON 序列化 -->
{{ data|tojson }}
<!-- 日期格式化 -->
{{ date|datetimeformat }} <!-- 需要自定义过滤器 -->链式调用:
{{ name|striptags|title|truncate(30) }}常用内置过滤器
| 过滤 | 说明 |
|---|---|
safe |
标记为安全 HTML,不转义 |
escape / e |
HTML 转义 |
capitalize |
首字母大写 |
lower / upper |
大小写转换 |
trim |
去除首尾空白 |
striptags |
去除 HTML 标签 |
length |
返回长度 |
join(", ") |
连接列表 |
first / last |
取首/尾元素 |
sort / reverse |
排序 |
batch(n) |
分批(每 n 个一组) |
slice(n) |
切片 |
format |
格式化字符串 |
replace(a, b) |
替换 |
wordcount |
单词计数 |
4.3 条件判断
{% if score >= 90 %}
<p>优秀</p>
{% elif score >= 60 %}
<p>及格</p>
{% else %}
<p>不及格</p>
{% endif %}支持的运算符:and、or、not、==、!=、<、>、in、is
{% if user.is_admin and user.is_active %}
<a href="/admin">管理后台</a>
{% endif %}
{% if "admin" in user.roles %}
<p>你有管理员权限</p>
{% endif %}4.4 循环
<ul>
{% for item in items %}
<li>{{ loop.index }}. {{ item.name }}</li>
{% else %}
<li>列表为空</li>
{% endfor %}
</ul>loop 变量在循环内部自动可用:
| 变量 | 说明 |
|---|---|
loop.index |
当前迭代次数(从 1 开始) |
loop.index0 |
当前迭代次数(从 0 开始) |
loop.revindex |
到结尾剩余次数(从 1 开始) |
loop.first |
是否为第一项 |
loop.last |
是否为最后一项 |
loop.length |
序列总长度 |
loop.cycle("a", "b") |
循环取值(奇偶行变色常用) |
奇偶行变色:
{% for item in items %}
<tr class="{{ loop.cycle('even', 'odd') }}">
<td>{{ item.name }}</td>
</tr>
{% endfor %}字典循环:
{% for key, value in my_dict.items() %}
<dt>{{ key }}</dt>
<dd>{{ value }}</dd>
{% endfor %}4.5 变量赋值
{% set title = "首页" %}
{% set nums = [1, 2, 3] %}
{% set fullname = user.first_name ~ " " ~ user.last_name %}~ 是字符串连接运算符。
4.6 空白控制
默认 Jinja2 会保留标签外的空白。可以用 - 去除:
{% for item in items -%}
{{ item }}
{%- endfor %}{%-去掉标签前的空白-%}去掉标签后的空白
五、模板继承
模板继承是 Jinja2 最强大的特性,通过 extends 和 block 实现布局复用。
5.1 基础布局 base.html
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}默认标题{% endblock %}</title>
<link rel="stylesheet" href="/style.css">
{% block extra_head %}{% endblock %}
</head>
<body>
<header>
{% block header %}站点标题{% endblock %}
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
{% block footer %}
© 2026 版权所有
{% endblock %}
</footer>
{% block scripts %}{% endblock %}
</body>
</html>5.2 子模板
{% extends "base.html" %}
{% block title %}首页 - 我的站点{% endblock %}
{% block content %}
<h1>欢迎</h1>
<p>这是首页内容</p>
{% endblock %}
{% block scripts %}
<script src="/home.js"></script>
{% endblock %}5.3 super() 保留父块内容
{% block footer %}
{{ super() }} <!-- 保留父级的 © 版权 -->
<p><a href="/about">关于我们</a></p>
{% endblock %}5.4 block 嵌套与作用域
{% block outer %}
外层内容
{% block inner %}
内层内容
{% endblock %}
{% endblock %}子模板可以覆盖任意层级的 block,也可以只覆盖内层:
{% extends "base.html" %}
{% block inner %}仅覆盖内层{% endblock %}六、宏(Macro)
宏类似于函数,用于复用 HTML 片段:
6.1 定义宏
{# macros.html #}
{% macro input(name, value="", type="text", size=20) %}
<input type="{{ type }}" name="{{ name }}"
value="{{ value|e }}" size="{{ size }}">
{% endmacro %}
{% macro render_card(title, body, class="") %}
<div class="card {{ class }}">
<div class="card-header">{{ title }}</div>
<div class="card-body">{{ body }}</div>
</div>
{% endmacro %}6.2 使用宏
{% import "macros.html" as macros %}
{{ macros.input("username", type="text") }}
{{ macros.input("password", type="password", size=40) }}
{{ macros.render_card("欢迎", "这是卡片内容", class="highlight") }}也可以从模板中直接导入特定宏:
{% from "macros.html" import input, render_card %}
{{ input("email") }}6.3 宏的 caller 回调
{% macro card(title) %}
<div class="card">
<h3>{{ title }}</h3>
<div class="card-body">{{ caller() }}</div>
</div>
{% endmacro %}{% call card("通知") %}
<p>这是卡片内部的内容</p>
{% endcall %}七、包含(Include)
{% include "header.html" %}
{% include "sidebar.html" ignore missing %}
{% include "ad.html" ignore missing with context %}ignore missing— 文件不存在时静默忽略with context— 传入当前上下文(默认行为)without context— 不传入当前上下文
八、自定义扩展
8.1 自定义过滤器
from jinja2 import Environment
def datetimeformat(value, format="%Y-%m-%d"):
return value.strftime(format)
def pluralize(count, singular="", plural="s"):
return singular if count == 1 else plural
# 注册
env = Environment()
env.filters["datetimeformat"] = datetimeformat
env.filters["pluralize"] = pluralize模板中使用:
{{ article.created_at|datetimeformat("%Y年%m月%d日") }}
{{ items|length }} item{{ items|length|pluralize("", "s") }}8.2 自定义测试器
def is_admin(user):
return user.role == "admin"
env.tests["admin"] = is_admin{% if user is admin %}
<a href="/admin">管理后台</a>
{% endif %}8.3 自定义全局函数
def static(path):
return f"/static/{path}"
env.globals["static"] = static<img src="{{ static('logo.png') }}" alt="Logo">九、本站实战案例
本站(markdown-cms)大量使用了 Jinja2 的特性:
9.1 基础布局
# templates/base.html
# 包含了导航栏、搜索面板、主题切换、页脚
# 用 {% block content %} 让子页面填充内容9.2 文章列表渲染
# article_service.py
env = Environment(loader=FileSystemLoader("templates/"))
content = f'''
<div class="page-header">
<h2>最新文章</h2>
<span class="page-count">共 {total} 篇</span>
</div>
<div class="article-list">
{cards_html}
</div>
'''
return env.get_template("base.html").render(title="首页", content=content)9.3 工具页模板继承
所有工具页(密码生成器、打包工具、Token 计算器等)都通过 env.get_template('base.html').render(...) 渲染,自动继承导航栏和页脚。
十、性能优化
10.1 模板缓存
env = Environment(
loader=FileSystemLoader("templates/"),
auto_reload=False, # 生产环境关闭自动重载
cache_size=50, # 最多缓存 50 个模板
)10.2 字节码缓存
from jinja2 import Environment, FileSystemLoader
from jinja2.bccache import FileSystemBytecodeCache
bccache = FileSystemBytecodeCache("cache/", "%s.cache")
env = Environment(
loader=FileSystemLoader("templates/"),
bytecode_cache=bccache, # 缓存编译后的字节码
)10.3 预编译模板
# 批量编译所有模板,减少首次渲染延迟
templates = env.list_templates()
for name in templates:
env.get_template(name)十一、常见问题与陷阱
11.1 UndefinedError 未定义变量
<!-- 访问不存在的变量不会报错,返回空字符串 -->
{{ not_exist }} <!-- 输出空 -->
<!-- 但如果访问不存在的属性/方法会报错 -->
{{ user.not_exist_attr }} <!-- UndefinedError -->解决方案:使用 default 过滤器或自定义 undefined 类:
env = Environment(undefined=DebugUndefined) # 开发模式
env = Environment(undefined=StrictUndefined) # 严格模式
env = Environment(undefined=ChainableUndefined) # 链式安全11.2 HTML 转义
# 默认 autoescape=True 时,{{ }} 自动转义
# 如果内容是安全 HTML,用 safe 过滤器
{{ "<b>粗体</b>"|safe }}11.3 XSS 防护
永远不要对用户输入使用 |safe,除非你 100% 确定内容是安全的。使用 bleach 库清理用户输入的 HTML:
import bleach
cleaned = bleach.clean(user_input, tags=["b", "i", "a"], attributes={"a": ["href"]})11.4 模板中调用 Python 方法
<!-- 支持调用无参数方法 -->
{{ user.get_full_name() }}
<!-- 不支持带参数方法(除非自定义全局函数) -->11.5 循环中修改变量无效
{% set total = 0 %}
{% for item in items %}
{% set total = total + item.price %} <!-- 不会生效! -->
{% endfor %}
{{ total }} <!-- 输出 0 -->解决方案:在 Python 端处理好数据再传给模板。
十二、与 Web 框架集成
Flask
Flask 内置 Jinja2,自动配置:
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def index():
return render_template("index.html", title="首页", items=items)
@app.template_filter("datetimeformat")
def datetimeformat(value, format="%Y-%m-%d"):
return value.strftime(format)Django(需安装第三方库)
pip install django-jinja# settings.py
TEMPLATES = [
{
"BACKEND": "django_jinja.backend.Jinja2Backend",
"DIRS": [BASE_DIR / "jinja2"],
"APP_DIRS": True,
},
]FastAPI / Starlette
from fastapi import FastAPI
from fastapi.templating import Jinja2Templates
app = FastAPI()
templates = Jinja2Templates(directory="templates/")
@app.get("/")
async def index(request):
return templates.TemplateResponse("index.html", {
"request": request,
"title": "首页"
})十三、总结
Jinja2 是一个功能完善、性能优秀的模板引擎,核心优势在于:
- 模板继承 — 避免重复编写公共布局
- 过滤器系统 — 灵活的数据格式化
- 沙箱安全 — 默认 HTML 转义防止 XSS
- 高性能 — 字节码缓存 + 编译执行
- 生态完善 — Flask/FastAPI 等框架原生支持
在实际项目中,建议:
- 始终开启
autoescape - 善用模板继承减少重复代码
- 复杂逻辑放在 Python 端,模板只做展示
- 自定义过滤器/全局函数可以让模板更简洁
掌握 Jinja2,是 Python Web 开发的必修课 🎯