Jinja2如何优雅获取嵌套对象的第一个已定义属性并避免未定义报错
Jinja2 嵌套属性优先级取值的优雅实现
报错根源
Jinja2 默认配置下,只要链式属性访问的任意一层不存在对应键/属性,就会立刻抛出 UndefinedError,不会执行后续的 or 兜底逻辑;如果手写多层 is defined 判断会产生大量冗余分支,完全不符合简洁的渲染需求。
注意:原代码中直接
from jinja2 import Template是Jinja提供的快捷创建模板的方法,内部默认使用普通的不可链式遍历的Undefined类,无法适配嵌套不存在属性的场景。
方案一:零代码改动适配原有写法(推荐)
利用Jinja2 2.11内置的 ChainableUndefined 类替换默认的未定义处理逻辑,就可以完全沿用你原来写的模板字符串,不需要加任何判断分支。
实现代码:
from jinja2 import Environment, ChainableUndefined # 初始化自定义模板环境,替换默认的未定义类 env = Environment(undefined=ChainableUndefined) o1 = { "a" : { "b" : { "c" : "John Doe" } } } o2 = { "e" : { "f" : "John Doe" } } o3 = { } # 模板字符串和最初的写法完全一致,不需要修改 template = env.from_string("""Hello {{ o.a.b.c or o.e.f or "John Doe" }}!""") for o in [o1, o2, o3]: r = template.render(o=o) print(r)
运行后三个对象都会正常输出 Hello John Doe!。
方案优势
- 完全保留原本期望的链式
or优先级写法,没有任何冗余判断 - 不会隐式吞错:如果最终的兜底值也没命中,未定义的表达式依然会按照原有规则抛出错误,不影响调试
- 不需要写自定义逻辑,直接使用内置能力实现
方案二:自定义极简嵌套取值过滤器
如果不想修改全局模板环境的未定义配置,可以写一个轻量自定义过滤器,专门处理嵌套路径的优先级取值,模板写法会更简洁统一。
实现代码:
def nested_pick(obj, *value_paths): # 最后一个参数作为全局兜底默认值 default = value_paths[-1] if value_paths else None paths = value_paths[:-1] if len(value_paths) > 1 else [] for path in paths: cur = obj for key in path.split("."): # 逐层级取值,碰到不存在的键立刻终止当前路径的遍历 cur = cur.get(key) if isinstance(cur, dict) else None if cur is None: break if cur is not None: return cur return default # 将过滤器注册到模板环境 env.filters["nested_pick"] = nested_pick
模板侧的简化写法:
Hello {{ o|nested_pick("a.b.c", "e.f", "John Doe") }}!
只需要把所有候选的嵌套路径按优先级写在参数里,最后一个参数作为兜底默认值,就可以实现完全等价的取值逻辑,不需要在模板里写任何分支判断。
内容的提问来源于stack exchange,提问作者Setop。
相关产品推荐
相关产品推荐

