Python-Jinja2 基础

第一章:环境配置与模板加载

Q1:如何创建基础的 Jinja2 环境并加载模板文件?

A: 使用 Environment 配合 FileSystemLoader 是最常见的加载方式。

from jinja2 import Environment, FileSystemLoader

# 1. 创建加载器,指向模板文件所在目录
loader = FileSystemLoader('templates')

# 2. 创建环境对象
env = Environment(loader=loader)

# 3. 加载具体模板
template = env.get_template('hello.html')

# 4. 渲染并传入变量
output = template.render(name='World')
print(output)  # 输出取决于模板内容

关键概念:

  • FileSystemLoader:从文件系统目录加载模板
  • Environment:Jinja2 的核心配置中心,管理加载器、过滤器、全局变量等
  • get_template():按文件名从加载器中获取模板对象
  • render():执行模板渲染,返回字符串

Q2:除了文件系统,还有哪些模板加载方式?

A: Jinja2 提供了多种内置加载器:

加载器 用途 示例场景
FileSystemLoader 从目录加载 .html 文件 Web 项目模板目录
PackageLoader 从 Python 包内加载 发布为 pip 包的库项目
DictLoader 从字典中加载模板字符串 单元测试、动态模板
FunctionLoader 通过回调函数加载 从数据库/网络加载模板
ChoiceLoader 组合多个加载器 先查缓存目录,再查默认目录
PrefixLoader 按前缀路由到不同加载器 多主题/多版本模板管理
from jinja2 import DictLoader, Environment

# DictLoader 示例:直接传入模板字典
templates = {
    'hello.html': '<h1>Hello {{ name }}!</h1>'
}
env = Environment(loader=DictLoader(templates))
template = env.get_template('hello.html')
print(template.render(name='Jinja2'))  # <h1>Hello Jinja2!</h1>

Q3:Environment 有哪些常用配置参数?

A:

env = Environment(
    loader=FileSystemLoader('templates'),
    autoescape=True,           # 自动转义HTML(防XSS)
    trim_blocks=True,          # 去除标签后的第一个空行
    lstrip_blocks=True,        # 去除块前的空格
    enable_async=True,         # 启用异步支持(async/await)
    cache_size=400,            # 模板缓存大小,0=不缓存,-1=无限制
    auto_reload=True,          # 开发时自动重载修改的模板
    undefined=StrictUndefined  # 变量未定义时严格报错
)

重点参数说明:

  • trim_blocks=True:移除 {% %} 标签后的换行符,解决模板渲染后多余空行问题
  • lstrip_blocks=True:配合 trim_blocks,移除块标签前的缩进空格
  • undefined:控制未定义变量的行为
    • Undefined(默认):静默处理,返回空字符串
    • DebugUndefined:返回调试信息(如 {{ missing_var }}
    • StrictUndefined:抛出 UndefinedError 异常(推荐生产环境使用)

第二章:模板语法基础

Q4:Jinja2 模板中如何输出变量和表达式?

A: 使用双大括号 {{ }} 进行变量插值和表达式计算。

<!-- templates/user.html -->
<h1>{{ user.name }}</h1>
<p>年龄:{{ user.age }}</p>
<p>明年年龄:{{ user.age + 1 }}</p>
<p>全名:{{ user.first_name ~ ' ' ~ user.last_name }}</p>
<p>是否存在:{{ user is defined }}</p>

语法要点:

语法 说明
{{ var }} 输出变量
{{ var.attr }} 访问对象属性(等价于 Python 的 var.attrvar['attr']
{{ var['key'] }} 字典/映射访问
{{ a ~ b }} 字符串拼接(~ 运算符)
{{ a + b }} 数学运算
{{ func() }} 调用函数

Q5:如何在模板中使用条件判断?

A: 使用 {% if %} 控制结构。

{% if user.is_vip %}
    <p>欢迎 VIP 用户!</p>
{% elif user.score > 100 %}
    <p>您的高级会员</p>
{% else %}
    <p>普通用户</p>
{% endif %}

支持的条件表达式:

{% if value is none %}           <!-- 判断是否为 None -->
{% if value is defined %}        <!-- 判断变量是否定义 -->
{% if value is string %}         <!-- 判断是否为字符串 -->
{% if value is number %}         <!-- 判断是否为数字 -->
{% if value is iterable %}       <!-- 判断是否可迭代 -->
{% if value is mapping %}        <!-- 判断是否为字典/映射 -->
{% if 'key' in dict %}           <!-- 成员判断 -->

Q6:如何在模板中循环遍历数据?

A: 使用 {% for %} 循环。

<ul>
{% for item in items %}
    <li>{{ loop.index }}. {{ item.name }} - ¥{{ item.price }}</li>
{% else %}
    <li>暂无商品</li>
{% endfor %}
</ul>

loop 对象的常用属性:

属性 说明
loop.index 当前迭代次数(从 1 开始)
loop.index0 当前迭代次数(从 0 开始)
loop.first 是否为第一次迭代
loop.last 是否为最后一次迭代
loop.length 可迭代对象的总长度
loop.revindex 反向索引(从 1 开始)
loop.previtem 上一个条目
loop.nextitem 下一个条目

字典遍历:

{% for key, value in user.items() %}
    <p>{{ key }}: {{ value }}</p>
{% endfor %}

Q7:如何在模板中定义和复用代码块(宏)?

A: 使用 {% macro %} 定义可复用的模板函数。

<!-- 定义宏 -->
{% macro input(name, value='', type='text', size=20) %}
    <input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}">
{% endmacro %}

<!-- 调用宏 -->
<p>{{ input('username') }}</p>
<p>{{ input('password', type='password') }}</p>

宏的高级用法:

<!-- 接收可变数量的位置参数和关键字参数 -->
{% macro dialog(title, *contents, **kwargs) %}
<div class="dialog" {% if kwargs.class %}class="{{ kwargs.class }}"{% endif %}>
    <h2>{{ title }}</h2>
    {% for content in contents %}
        <p>{{ content }}</p>
    {% endfor %}
</div>
{% endmacro %}

第三章:模板继承与包含

Q8:如何实现模板的继承和布局复用?

A: 使用 {% extends %}{% block %}{% super() %} 实现模板继承。

<!-- templates/base.html (基础布局) -->
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}默认标题{% endblock %}</title>
    {% block head %}
    <link rel="stylesheet" href="style.css">
    {% endblock %}
</head>
<body>
    <div id="content">
        {% block content %}{% endblock %}
    </div>
    <footer>
        {% block footer %}
        <p>© 2026 版权所有</p>
        {% endblock %}
    </footer>
</body>
</html>
<!-- templates/child.html (子模板) -->
{% extends "base.html" %}

{% block title %}首页 - 我的网站{% endblock %}

{% block head %}
    {{ super() }}  <!-- 保留父模板的 head 内容 -->
    <script src="custom.js"></script>
{% endblock %}

{% block content %}
    <h1>欢迎来到首页</h1>
{% endblock %}

继承规则:

  • 子模板必须第一行使用 {% extends %}
  • 子模板只能定义 block 中声明的内容,块外的内容会被忽略
  • {{ super() }} 用于保留父模板中该块的原始内容

Q9:如何在模板中包含其他模板片段?

A: 使用 {% include %} 引入其他模板。

<!-- 包含完整模板 -->
{% include 'header.html' %}

<!-- 包含时忽略缺失的模板(不报错) -->
{% include 'sidebar.html' ignore missing %}

<!-- 包含时传入额外变量 -->
{% include 'helper.html' with context %}      <!-- 传递当前上下文 -->
{% include 'helper.html' without context %}   <!-- 不传递当前上下文 -->

与继承的区别:

  • extends:建立父子关系,子模板填充父模板的 block
  • include:简单的模板拼接,被包含的模板是独立渲染的片段

第四章:过滤器(Filters)

Q10:Jinja2 有哪些常用的内置过滤器?

A:

过滤器 功能 示例
{{ var | default('N/A') }} 设置默认值 变量未定义或为空时显示 ‘N/A’
{{ var | escape }}e HTML 转义 < 转为 &lt;
{{ var | safe }} 标记安全 禁止转义,直接输出原始 HTML
{{ list | length }} 获取长度 返回列表/字符串长度
{{ list | first }} 获取首元素
{{ list | last }} 获取末元素
{{ list | join(', ') }} 拼接字符串 将列表用指定分隔符连接
{{ list | sort }} 排序 支持 sort(attribute='name')
{{ list | reverse }} 反转
{{ list | unique }} 去重
{{ list | selectattr('active') }} 按属性筛选 筛选 active 为真的对象
{{ list | rejectattr('deleted') }} 按属性排除
{{ str | upper }} 转大写
{{ str | lower }} 转小写
{{ str | title }} 单词首字母大写
{{ str | trim }} 去除首尾空格
{{ str | replace('a', 'b') }} 替换
{{ str | truncate(20) }} 截断 超过 20 字符显示 ...
{{ num | round(2) }} 四舍五入 保留 2 位小数
{{ num | int }} 转整数
{{ num | float }} 转浮点数
{{ dict | tojson }} 转 JSON 字符串 常用于 <script> 标签内
{{ html | striptags }} 去除 HTML 标签
{{ var | batch(3) }} 分批处理 将列表按每 3 个一组切分
{{ var | slice(3) }} 切片分组 将列表均匀分成 3 组
{{ var | map(attribute='name') }} 提取属性 提取列表中每个对象的 name

链式调用:

{{ users | selectattr('is_active') | map(attribute='name') | sort | join(', ') }}

Q11:如何在 Environment 中注册自定义过滤器?

A:

from jinja2 import Environment, FileSystemLoader
import datetime

env = Environment(loader=FileSystemLoader('templates'))

# 方法 1:直接注册函数为过滤器
def format_datetime(value, format='%Y-%m-%d %H:%M'):
    """将时间戳或 datetime 对象格式化为字符串"""
    if isinstance(value, datetime.datetime):
        return value.strftime(format)
    return value

env.filters['datetime'] = format_datetime

# 方法 2:使用装饰器(更优雅)
@env.filter('currency')
def format_currency(value, symbol='¥'):
    """格式化为货币显示"""
    return f"{symbol}{value:,.2f}"

# 模板中使用
# {{ created_at | datetime }}           -> 2026-05-07 19:39
# {{ created_at | datetime('%Y年%m月%d日') }}  -> 2026年05月07日
# {{ price | currency }}                -> ¥1,234.50
# {{ price | currency('$') }}           -> $1,234.50

第五章:自动转义与安全

Q12:Jinja2 如何防止 XSS 攻击?自动转义是如何工作的?

A:

# 开启自动转义(推荐用于 HTML 模板)
env = Environment(
    loader=FileSystemLoader('templates'),
    autoescape=True  # 或 autoescape='html'
)

转义行为规则:

输入类型 自动转义行为
字符串 转义 HTML 特殊字符(<, >, &, "
数字 不转义(本身就是安全的)
Markup 对象 不转义(标记为安全)
from jinja2 import Markup

# 在 Python 中标记字符串为安全(谨慎使用!)
safe_html = Markup('<strong>粗体</strong>')
template.render(content=safe_html)  # 会直接输出 <strong>标签

# 在模板中标记安全(仅当你确定数据源可信时)
# {{ html_content | safe }}

手动控制转义:

<!-- 强制转义 -->
{{ user_input | escape }}

<!-- 强制不转义(危险!确保数据源可信) -->
{{ trusted_html | safe }}

第六章:全局变量与测试

Q13:如何在所有模板中注入全局可用的变量或函数?

A:

from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader('templates'))

# 注入全局变量
env.globals['site_name'] = '我的网站'
env.globals['current_year'] = 2026

# 注入全局函数
def get_asset_url(filename):
    return f"/static/{filename}"

env.globals['asset'] = get_asset_url

# 模板中直接使用
# <link rel="stylesheet" href="{{ asset('style.css') }}">
# <footer>© {{ current_year }} {{ site_name }}</footer>

Q14:什么是测试(Tests)?与过滤器有什么区别?

A: 测试用于条件判断,返回布尔值;过滤器用于数据转换。

{% if number is odd %}
    <p>奇数</p>
{% endif %}

{% if users is iterable %}
    <p>可迭代对象</p>
{% endif %}

内置测试:

测试 说明
is defined 变量是否定义
is none 是否为 None
is string 是否为字符串
is number 是否为数字
is iterable 是否可迭代
is mapping 是否为字典
is sequence 是否为序列(列表/元组/字符串)
is sameas(obj) 是否与 obj 是同一对象
is odd 是否为奇数
is even 是否为偶数
is divisibleby(n) 是否能被 n 整除

注册自定义测试:

def is_image_file(filename):
    return filename.lower().endswith(('.png', '.jpg', '.jpeg', '.gif'))

env.tests['image'] = is_image_file

# 模板中使用:{% if filename is image %}...{% endif %}

第七章:高级用法

Q15:如何在模板中执行 Python 表达式或调用方法?

A: Jinja2 默认限制了部分 Python 语法,但支持以下操作:

<!-- 调用对象方法(需要方法无副作用且返回字符串/值) -->
{{ user.get_display_name() }}

<!-- 使用下标访问 -->
{{ users[0].name }}

<!-- 三元表达式 -->
{{ '是' if user.is_vip else '否' }}

<!-- 内联 if(类似 Python 的条件表达式) -->
{{ user.nickname or user.username }}

注意: Jinja2 的沙箱机制默认不允许直接调用 Python 内置函数(如 rangedict 等)。如需使用,需要通过 Environment 注入:

env.globals.update(
    range=range,
    dict=dict,
    len=len,
    enumerate=enumerate,
    zip=zip
)

Q16:如何渲染模板字符串(不通过文件)?

A: 使用 Template 类直接渲染字符串。

from jinja2 import Template

template = Template('Hello {{ name }}!')
result = template.render(name='World')
print(result)  # Hello World!

注意: 字符串模板不通过 Environment,因此无法使用自定义过滤器和全局变量。如需使用,应从环境创建:

from jinja2 import Environment

env = Environment()
template = env.from_string('Hello {{ name | upper }}!')
print(template.render(name='world'))  # Hello WORLD!

Q17:如何处理模板渲染中的异常?

A:

from jinja2 import Environment, TemplateNotFound, UndefinedError

env = Environment(loader=FileSystemLoader('templates'))

try:
    template = env.get_template('missing.html')
except TemplateNotFound as e:
    print(f"模板未找到: {e}")

try:
    template = env.get_template('hello.html')
    result = template.render()
except UndefinedError as e:
    print(f"变量未定义: {e}")
except Exception as e:
    print(f"渲染错误: {e}")

第八章:完整实战示例

Q18:能否提供一个完整的 Jinja2 使用示例?

A:

# app.py
from jinja2 import Environment, FileSystemLoader, StrictUndefined
import datetime

# 1. 配置环境
env = Environment(
    loader=FileSystemLoader('templates'),
    autoescape=True,
    trim_blocks=True,
    lstrip_blocks=True,
    undefined=StrictUndefined  # 严格模式:未定义变量直接报错
)

# 2. 注册自定义过滤器
@env.filter('datetime')
def format_datetime(value, fmt='%Y-%m-%d'):
    if isinstance(value, datetime.datetime):
        return value.strftime(fmt)
    return value

# 3. 注册全局函数
env.globals['now'] = lambda: datetime.datetime.now()

# 4. 准备数据
users = [
    {'name': '张三', 'age': 28, 'is_vip': True, 'created_at': datetime.datetime(2026, 1, 15)},
    {'name': '李四', 'age': 22, 'is_vip': False, 'created_at': datetime.datetime(2026, 3, 20)},
    {'name': '王五', 'age': 35, 'is_vip': True, 'created_at': datetime.datetime(2025, 8, 10)},
]

# 5. 渲染模板
template = env.get_template('users.html')
html = template.render(
    page_title='用户列表',
    users=users
)
print(html)
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}{% endblock %} - {{ site_name }}</title>
    <style>
        .vip { color: gold; }
        table { border-collapse: collapse; width: 100%; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #f2f2f2; }
    </style>
</head>
<body>
    {% block content %}{% endblock %}
    <footer>
        <p>生成时间:{{ now() | datetime('%Y-%m-%d %H:%M:%S') }}</p>
    </footer>
</body>
</html>
<!-- templates/users.html -->
{% extends "base.html" %}

{% block title %}{{ page_title }}{% endblock %}

{% block content %}
<h1>{{ page_title }}</h1>

<table>
    <thead>
        <tr>
            <th>#</th>
            <th>姓名</th>
            <th>年龄</th>
            <th>会员状态</th>
            <th>注册时间</th>
        </tr>
    </thead>
    <tbody>
        {% for user in users %}
        <tr class="{{ 'vip' if user.is_vip else '' }}">
            <td>{{ loop.index }}</td>
            <td>{{ user.name }}</td>
            <td>{{ user.age }}</td>
            <td>
                {% if user.is_vip %}
                    ⭐ VIP会员
                {% else %}
                    普通用户
                {% endif %}
            </td>
            <td>{{ user.created_at | datetime('%Y年%m月%d日') }}</td>
        </tr>
        {% else %}
        <tr>
            <td colspan="5" style="text-align: center;">暂无用户数据</td>
        </tr>
        {% endfor %}
    </tbody>
</table>

<p>平均年龄:{{ (users | map(attribute='age') | sum) / (users | length) }} 岁</p>
{% endblock %}

附录:速查表

场景 语法
输出变量 {{ var }}
执行逻辑 {% if %}, {% for %}, {% macro %}
注释 {# 这是注释 #}
继承 {% extends %} + {% block %}
包含 {% include %}
过滤器 {{ var | filter }}
测试 {% if var is test %}
字符串拼接 {{ 'a' ~ 'b' }}
忽略转义 {{ var | safe }}
默认值 {{ var | default('N/A') }}
Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐