bigywater.tech
Flask · 51

Jinja2 Template Loader

模板文件是怎么被找到、读进来、变成 Python 代码的?Flask 的加载器和 Jinja 的有什么关系?能不能换成数据库或字典?这篇从加载器的接口讲起,一直打印到编译出来的 Python 源码。

2 个
DispatchingJinjaLoader 向下委托的来源:应用的目录 + 各蓝图的目录(本篇演示了应用侧)
400
jinja_env 编译缓存的默认容量
≈191×
编译一次相对渲染一次的耗时倍数

Flask 的模板环境

实测 · 本机执行输出
  app.jinja_env 类型: flask.templating.Environment | loader: DispatchingJinjaLoader
  app.jinja_loader: FileSystemLoader | searchpath: ['<项目>/templates']
  Flask 3.1 对 jinja_env 的默认选项:autoescape = method | auto_reload = False | cache_size = 400 | trim_blocks = False | lstrip_blocks = False | undefined = Undefined
  jinja_options: {}

上一篇:Content Negotiation;下一篇:Flask 模板搜索路径。

get_source 与 uptodate

实测 · 本机执行输出
  源码 = 'Hello {{ name }}!\n' | 文件名 = <项目>/templates/hello.html | uptodate() = True
  文件被修改后 uptodate() = False (返回 False 表示缓存的编译结果过期了)
  找不到 → TemplateNotFound: nope.html

内置加载器

实测 · 本机执行输出
  DictLoader: A:1
  ChoiceLoader:a.html → 覆盖的 A | hello.html → Hello x!
  PrefixLoader:ui/btn.html → <button> | mail/btn.html → 邮件按钮
  FunctionLoader:dyn/x → 来自函数:dyn/x | 调用记录 ['dyn/x']

在 Flask 里换加载器

实测 · 本机执行输出
  GET /h → 被覆盖:ann | GET /p → <html><p>ann</p></html>

编译成 Python

实测 · 本机执行输出
    from jinja2.runtime import LoopContext, Macro, Markup, Namespace, TemplateNotFound, TemplateReference, TemplateRuntimeError, Undefined, escape, identity, internalcode, markup_join, missing, str_join
    name = None
    def root(context, missing=missing, environment=environment):
        resolve = context.resolve_or_missing
        undefined = environment.undefined
        concat = environment.concat
        cond_expr_undefined = Undefined
        if 0: yield None
        l_0_name = resolve('name')
        l_0_items = resolve('items')
        try:
            t_1 = environment.filters['upper']
        except KeyError:
            @internalcode
            def t_1(*unused):
                raise TemplateRuntimeError("No filter named 'upper' found.")
        pass
        yield 'Hello '
        yield escape((undefined(name='name') if l_0_name is missing else l_0_name))
        yield '!'
        for l_1_i in (undefined(name='items') if l_0_items is missing else l_0_items):
            _loop_vars = {}

编译与渲染的耗时

实测 · 本机执行输出
  parse(只生成语法树)2.77 ms;compile(含代码生成与 Python 编译)8.36 ms;已编译后 render 0.044 ms;编译是渲染的 191 倍 —— 所以缓存编译结果很重要

分步演示

Jinja2 加载器
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动。Jinja2 3.1.6
    • 没有验证:PackageLoader 与 ModuleLoader(预编译成模块)的行为、从数据库读模板的缓存失效策略、异步模板(enable_async)的编译结果差异、不同 Jinja2 版本的生成代码形式
    京ICP备2026067502号-1京公网安备11010502063487号