bigywater.tech
Django · 86

自定义 Template Tag

simple_tag 和 inclusion_tag 有什么区别?返回 HTML 时怎么转义?能不能改模板变量?开销多大?这篇写几个小标签,把这些问题逐个跑出来。

as 变量
simple_tag 用 as 存结果,重复使用时只调用一次
转义
simple_tag 返回的字符串默认被转义;format_html 返回才保留标签,mark_safe + 拼接会 XSS
8 倍
inclusion_tag 相对直接写变量的渲染开销(20 处调用:201 vs 25 µs)

simple_tag

实测 · 本机执行输出
{% hello "ann" %}                 → Hi, ann
{% hello name greeting="Yo" %}     → Yo, bob
{% hello "cat" as msg %}{{ msg }}{{ msg }} → [Hi, cat][Hi, cat]
   ↑ as 之后用两次,tag 调用次数: 1
不用 as 写两次,tag 调用次数: 2

适合「算一个值输出」。要在同一页多次使用同一个结果,用 as。

输出转义

实测 · 本机执行输出
返回普通字符串 <b> → &lt;b&gt;&lt;i&gt;&lt;/b&gt;
format_html 返回 → <b><i></b>
mark_safe 返回(参数未转义!)→ <i><script>x</script></i>

simple_tag 的返回值会经过 conditional_escape:返回 SafeString 才保持原样。

takes_context

实测 · 本机执行输出
who(无 user 属性的 request) → anon
set_flag 之后同一模板里能读到吗: '[set-by-tag]'  ← 写进 context 的变量在当前作用域内有效
在 {% for %} 里 set_flag: '[]'  ← 循环里的写入在循环结束后不可见(context 被 push/pop)

写上下文会污染模板的变量空间,通常不推荐;确实需要时注意作用域(第 87 篇看块作用域)。

inclusion_tag

实测 · 本机执行输出
{% item "a" %} → <li>a|</li>
外层变量 site 默认不会传进去: (见上,|后面为空)
{% item_ctx "b" %} takes_context → <li>b|S</li>
循环里 4 次 inclusion_tag,tag 函数调用次数: 4

相当于一个「带自己上下文的 include」。

开销

实测 · 本机执行输出
20 处直接写 {{ v }}:              25 µs
20 处 simple_tag {% hello v %}:   29 µs
20 处 inclusion_tag {% item v %}: 201 µs

渲染 200 次取中位数,合成对比,SQLite 无关。

异常

实测 · 本机执行输出
ValueError: tag exploded

没有静默吞掉。

分步演示:自定义标签

自定义 Tag
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。用 engines["django"].from_string 渲染,标签库放在 app 的 templatetags 目录
    • 没有验证:simple_block_tag(Django 5.2 起)、tag 的 parser 级自定义(@register.tag)、inclusion_tag 的模板加载在无 cached 下的开销、标签参数的位置/关键字混用错误信息
    京ICP备2026067502号-1京公网安备11010502063487号