Jinja2 详细教程与完整语法文档
Jinja2 是 Python 生态中最流行的模板引擎之一,广泛应用于 Flask 、Django 、Ansible 、FastAPI 、MicroDot(单片机物联网框架) 等框架和工具中。
导言
最近在使用Jinja2 +wkhtmltoimage ,实现通过html 生成图片的应用,其中需要Jinja2 向html 中传入变量值。所以这里总结一下Jinja2 使用文档
wkhtmltoimage生成html图片
这里贴一下html生成图片核心代码,需要用户自行安装wkhtmltopdf,并配置好环境变量

python
import subprocess
import os
import sys
def generate_image(
html, output_path, img_width="1400", img_quality="70", img_format="png"
):
"""
将HTML转换为图片,依赖wkhtmltoimage
参数:
html (str): 要转换的HTML内容
output_path (str): 输出图片路径
返回:
str: 成功返回输出路径,失败返回None
异常:
会捕获并处理各种可能的错误,打印错误信息
"""
try:
# 检查wkhtmltoimage是否可用
try:
subprocess.run(
["wkhtmltoimage", "--version"],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
except FileNotFoundError:
raise RuntimeError("wkhtmltoimage未安装或未加入系统PATH环境变量")
except subprocess.CalledProcessError:
raise RuntimeError("wkhtmltoimage版本检查失败")
# 检查输出目录是否存在
output_dir = os.path.dirname(output_path) or "."
if not os.path.exists(output_dir):
os.makedirs(output_dir, exist_ok=True)
# 准备命令参数
cmd = [
"wkhtmltoimage",
"--format",
img_format,
"--width",
img_width,
"--quality",
img_quality,
"--disable-smart-width", # 避免内容被截断
"-", # 表示从标准输入读取HTML
output_path,
]
# 执行转换
process = subprocess.Popen(
cmd,
stdin=subprocess.PIPE,
# stdout=subprocess.PIPE,
# stderr=subprocess.PIPE,
universal_newlines=False,
)
# 写入HTML内容并等待完成
stdout, stderr = process.communicate(input=html.encode("utf-8"))
# 检查执行结果
if process.returncode != 0:
error_msg = stderr.decode("utf-8") if stderr else "未知错误"
raise RuntimeError(
f"图片生成失败 (返回码: {process.returncode}): {error_msg}"
)
# 检查输出文件是否生成
if not os.path.exists(output_path):
raise RuntimeError("图片文件未生成")
# 检查文件是否有效
if os.path.getsize(output_path) == 0:
os.remove(output_path)
raise RuntimeError("生成的图片文件为空")
return output_path
except Exception as e:
# 打印错误信息
print(f"错误: {str(e)}", file=sys.stderr)
# 尝试清理可能生成的不完整文件
if os.path.exists(output_path) and os.path.getsize(output_path) == 0:
try:
os.remove(output_path)
except:
pass
return None
一、Jinja2快速入门
1.1 安装与基础使用
python
pip install Jinja2
python
from jinja2 import Template
# 简单模板渲染
template = Template('Hello, {{ name }}!')
result = template.render(name='World')
print(result) # 输出: Hello, World!
1.2 核心语法标记
Jinja2 使用三种基本语法标记:
- 变量输出 :
{{ variable }} - 控制语句 :
{% statement %} - 注释 :
{# comment #}
二、完整语法详解
2.1 变量与表达式
变量使用双大括号输出,支持 Python 所有数据类型:
python
<!-- 字符串 -->
<p>{{ username }}</p>
<!-- 字典访问 -->
<p>邮箱: {{ user.email }}</p>
<!-- 列表索引 -->
<p>第一个元素: {{ items[0] }}</p>
<!-- 对象方法调用 -->
<p>时间: {{ datetime.now().strftime('%Y-%m-%d') }}</p>
表达式支持:数学运算、比较运算、逻辑运算、三元表达式等:
python
{{ 1 + 2 * 3 }} {# 输出: 7 #}
{{ "hello" if True else "world" }} {# 输出: hello #}
{{ user.age > 18 }} {# 输出: True #}
2.2 控制结构
条件语句 (if/elif/else)
python
{% if score >= 90 %}
<p class="excellent">优秀</p>
{% elif score >= 60 %}
<p class="pass">及格</p>
{% else %}
<p class="fail">不及格</p>
{% endif %}
循环语句 (for)
python
<ul>
{% for user in users %}
<li>{{ user.name }} - {{ user.email }}</li>
{% endfor %}
</ul>
循环控制变量 :loop对象提供循环状态信息
python
{% for item in items %}
<p>
当前索引: {{ loop.index }} (从1开始),
当前索引0: {{ loop.index0 }} (从0开始),
是否第一次: {{ loop.first }},
是否最后一次: {{ loop.last }},
剩余次数: {{ loop.revindex }}
</p>
{% endfor %}
循环控制语句
python
{% for item in items %}
{% if loop.index > 10 %}
{% break %} {# 跳出循环 #}
{% endif %}
{% if item.hidden %}
{% continue %} {# 跳过当前项 #}
{% endif %}
{{ item.name }}
{% endfor %}
2.3 过滤器 (Filters)
过滤器通过管道符 |应用,支持链式调用:
python
{{ "hello world" | upper }} {# 转大写: HELLO WORLD #}
{{ " hello " | trim }} {# 去空格: hello #}
{{ 3.14159 | round(2) }} {# 四舍五入: 3.14 #}
{{ items | length }} {# 列表长度 #}
{{ html_content | safe }} {# 禁用HTML转义 #}
{{ text | truncate(50) }} {# 截断文本 #}
{{ list | join(', ') }} {# 列表转字符串 #}
{{ value | default('N/A') }} {# 默认值 #}
链式调用示例:
python
{{ " hello world " | trim | upper | truncate(5) }}
{# 输出: HELLO #}
2.4 测试器 (Tests)
测试器用于条件判断,语法为 is:
python
{% if number is even %}
偶数
{% endif %}
{% if user is defined %}
用户已定义
{% endif %}
{% if value is none %}
值为空
{% endif %}
2.5 宏 (Macros)
宏是可重用的模板片段,类似函数:
python
{# 定义宏 #}
{% macro input_field(name, value='', type='text', class='') %}
<input type="{{ type }}" name="{{ name }}"
value="{{ value }}" class="{{ class }}">
{% endmacro %}
{# 使用宏 #}
{{ input_field('username') }}
{{ input_field('password', type='password', class='form-control') }}
带默认参数的宏:
python
{% macro card(title, content, color='primary') %}
<div class="card card-{{ color }}">
<h3>{{ title }}</h3>
<p>{{ content }}</p>
</div>
{% endmacro %}
2.6 模板继承
基础模板 (base.html)
python
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}默认标题{% endblock %}</title>
{% block head %}{% endblock %}
</head>
<body>
<header>{% block header %}导航栏{% endblock %}</header>
<main>
{% block content %}
<p>默认内容</p>
{% endblock %}
</main>
<footer>{% block footer %}页脚{% endblock %}</footer>
</body>
</html>
子模板继承
python
{% extends "base.html" %}
{% block title %}子页面标题{% endblock %}
{% block head %}
{{ super() }} {# 保留父模板内容 #}
<style>
.custom-style { color: red; }
</style>
{% endblock %}
{% block content %}
<h1>子页面内容</h1>
<p>这是子模板的内容</p>
{% endblock %}
继承要点:
extends必须是模板的第一个标签block定义可覆盖的区域super()调用父模板的块内容- 未覆盖的块使用父模板默认内容
2.7 模板包含
python
{# 包含其他模板文件 #}
{% include "header.html" %}
<main>主内容</main>
{% include "footer.html" %}
包含时可以传递变量:
python
{% include "user_card.html" with user=current_user %}
2.8 变量设置
在模板中定义变量:
python
{% set title = "页面标题" %}
{% set items = [1, 2, 3] %}
{{ title }} {# 输出: 页面标题 #}
2.9 空白控制
控制模板渲染后的空白字符:
python
{# 删除前导空白 #}
{% for item in items -%}
{{ item }}
{%- endfor %}
{# 输出: item1item2item3 #}
2.10 注释
注释不会出现在渲染结果中:
python
{# 这是单行注释 #}
{#
这是多行注释
不会输出到结果
#}
三、API 与高级用法
3.1 Environment 环境配置
python
from jinja2 import Environment, FileSystemLoader
# 创建环境
env = Environment(
loader=FileSystemLoader('templates'), # 模板加载路径
autoescape=True, # 自动HTML转义
trim_blocks=True, # 删除块后换行
lstrip_blocks=True, # 删除块前空格
undefined=StrictUndefined # 严格未定义变量处理
)
# 加载模板
template = env.get_template('index.html')
# 渲染
result = template.render(title='首页', users=user_list)
3.2 自定义过滤器
python
from jinja2 import Environment
def reverse_string(value):
"""反转字符串"""
return value[::-1]
def format_currency(amount):
"""格式化货币"""
return f"¥{amount:.2f}"
# 注册过滤器
env = Environment()
env.filters['reverse'] = reverse_string
env.filters['currency'] = format_currency
# 使用
template = env.from_string('{{ text|reverse }} {{ price|currency }}')
result = template.render(text='hello', price=99.99)
print(result) # 输出: olleh ¥99.99
3.3 自定义测试器
python
def is_even(value):
"""判断是否为偶数"""
return value % 2 == 0
def is_positive(value):
"""判断是否为正数"""
return value > 0
env.tests['even'] = is_even
env.tests['positive'] = is_positive
# 模板中使用
# {% if number is even %}...{% endif %}
3.4 全局函数
python
def get_current_time():
"""获取当前时间"""
from datetime import datetime
return datetime.now().strftime('%Y-%m-%d %H:%M:%S')
env.globals['current_time'] = get_current_time
# 模板中直接调用
# {{ current_time() }}
四、内置过滤器列表(常用)
Jinja2 常用内置过滤器列表
| 过滤器 | 说明 | 示例代码 | 输出结果 |
|---|---|---|---|
abs |
绝对值 | `{{ -5 | abs }}` |
capitalize |
首字母大写 | `{{ "hello" | capitalize }}` |
default |
设置默认值 | `{{ value | default("N/A") }}` |
escape |
HTML转义 | `{{ "<script>" | escape }}` |
first |
获取第一个元素 | `{{ [1,2,3] | first }}` |
float |
转为浮点数 | `{{ "3.14" | float }}` |
int |
转为整数 | `{{ "42" | int }}` |
join |
列表连接为字符串 | `{{ [1,2,3] | join(',') }}` |
last |
获取最后一个元素 | `{{ [1,2,3] | last }}` |
length |
获取长度 | `{{ "hello" | length }}` |
lower |
转小写 | `{{ "HELLO" | lower }}` |
map |
映射列表 | `{{ users | map(attribute='name') }}` |
random |
随机选择元素 | `{{ [1,2,3] | random }}` |
replace |
字符串替换 | `{{ "hello" | replace('h', 'j') }}` |
reverse |
反转 | `{{ "hello" | reverse }}` |
round |
四舍五入 | `{{ 3.14159 | round(2) }}` |
safe |
标记为安全HTML | `{{ html_content | safe }}` |
slice |
切片操作 | `{{ [1,2,3,4] | slice(1,3) }}` |
sort |
排序 | `{{ [3,1,2] | sort }}` |
striptags |
去除HTML标签 | `{{ "hello" | striptags }}` |
sum |
求和 | `{{ [1,2,3] | sum }}` |
title |
每个单词首字母大写 | `{{ "hello world" | title }}` |
trim |
去除首尾空格 | `{{ " hello " | trim }}` |
truncate |
截断文本 | `{{ text | truncate(20) }}` |
unique |
去重 | `{{ [1,2,2,3] | unique }}` |
upper |
转大写 | `{{ "hello" | upper }}` |
urlencode |
URL编码 | `{{ "hello world" | urlencode }}` |
五、内置测试器列表
| 测试器 | 说明 | 示例 |
|---|---|---|
defined |
是否已定义 | {% if var is defined %} |
divisibleby |
是否可被整除 | {% if 10 is divisibleby(5) %} |
even |
是否为偶数 | {% if num is even %} |
iterable |
是否可迭代 | {% if obj is iterable %} |
lower |
是否小写 | {% if str is lower %} |
mapping |
是否为映射类型 | {% if obj is mapping %} |
none |
是否为None | {% if val is none %} |
number |
是否为数字 | {% if val is number %} |
odd |
是否为奇数 | {% if num is odd %} |
sameas |
是否相同对象 | {% if obj1 is sameas(obj2) %} |
sequence |
是否为序列 | {% if obj is sequence %} |
string |
是否为字符串 | {% if val is string %} |
undefined |
是否未定义 | {% if var is undefined %} |
upper |
是否大写 | {% if str is upper %} |
六、实战示例
6.1 用户列表页面
Python 代码:
python
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader('templates'))
template = env.get_template('user_list.html')
users = [
{'name': '张三', 'age': 25, 'email': 'zhangsan@example.com'},
{'name': '李四', 'age': 30, 'email': 'lisi@example.com'},
{'name': '王五', 'age': 28, 'email': 'wangwu@example.com'}
]
result = template.render(users=users, title='用户列表')
print(result)
模板文件 (user_list.html) :
python
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
<style>
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
tr:nth-child(even) { background-color: #f9f9f9; }
</style>
</head>
<body>
<h1>{{ title }}</h1>
<table>
<tr>
<th>序号</th>
<th>姓名</th>
<th>年龄</th>
<th>邮箱</th>
</tr>
{% for user in users %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ user.name }}</td>
<td>{{ user.age }}</td>
<td>{{ user.email }}</td>
</tr>
{% else %}
<tr>
<td colspan="4">暂无用户数据</td>
</tr>
{% endfor %}
</table>
<p>共 {{ users|length }} 条记录</p>
</body>
</html>
6.2 配置文件生成
数据文件 (config.yaml) :
python
servers:
- name: web-server
ip: 192.168.1.10
port: 80
services: ['nginx', 'php-fpm']
- name: db-server
ip: 192.168.1.20
port: 3306
services: ['mysql']
模板文件 (server.conf.j2) :
python
# 自动生成的服务器配置
# 生成时间: {{ now() }}
{% for server in servers %}
server {
server_name {{ server.name }};
listen {{ server.ip }}:{{ server.port }};
{% for service in server.services %}
# {{ service }} 服务配置
{% endfor %}
}
{% endfor %}
生成脚本:
python
import yaml
from jinja2 import Environment, FileSystemLoader
from datetime import datetime
def now():
return datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 加载数据
with open('config.yaml', 'r') as f:
data = yaml.safe_load(f)
# 配置环境
env = Environment(loader=FileSystemLoader('templates'))
env.globals['now'] = now
# 渲染模板
template = env.get_template('server.conf.j2')
config_content = template.render(servers=data['servers'])
# 保存结果
with open('nginx.conf', 'w') as f:
f.write(config_content)
结尾
挺好用的