【问题标题】:Keep undefined variables保留未定义的变量
【发布时间】:2015-02-24 09:49:24
【问题描述】:

我有兴趣在多个步骤中渲染模板或在 Jinja2 中保留未定义变量的标签。我相信这不仅意味着创建“UndefinedSilent”类(因此模板不会因丢失数据而崩溃),而且如果标签丢失,还需要使用适当的变量名。

示例: 假设我们在上下文中包含 name = "Test",但缺少 quantity

给定以下模板:

<p>{{name}} has {{quantity}}</p>

渲染后,我需要模板变成:

<p>test has {{quantity}}</p>

有谁知道这是否可以实现?

【问题讨论】:

    标签: python jinja2 undefined


    【解决方案1】:

    为环境中的未定义命名参数提供DebugUndefined,显然可以解决问题。呈现的模板保留了{{&lt;undefined variable}}

    喜欢这里:

    from jinja2 import Environment, BaseLoader, DebugUndefined
    
    rtemplate = Environment(loader=BaseLoader,undefined=DebugUndefined).from_string("{{ a }} is defined, but {{ b}} is undefined")
    print(rtemplate.render({"a":"a"}))
    

    结果是:

    a is defined, but {{ b }} is undefined
    

    【讨论】:

    • 如果您使用不同的 variable_start_string(或 end,resp.),这将无法正常工作。显然{{ &lt;var&gt; }} 格式在这里是硬编码的(看看class DebugUndefined):-/
    • 您知道如何使这项工作适用于多级未定义变量吗?我可以让未定义的“{{ b }}”正常工作,但不能为“{{ b.c }}”工作,这给了我错误 jinja2.exceptions.UndefinedError: 'b' is undefined
    • 我最终只使用了我自己的变量起始字符串和不同于“{{”和“}}”的变量结束字符串。我用了 ”{@”。这适用于我的用例,因为我事先知道哪些变量将未定义,但我想对于无法事先知道哪些变量将未定义的用例,找到一个更通用的解决方案仍然会很好与 DebugUndefined 一样方便,但也适用于嵌套的未定义变量。
    【解决方案2】:

    这是另一种在渲染后保留未定义的双花括号表达式的方法,包括那些包含“多级”(点标记)引用以及任何其他引用的方法。

    Willy Pregliasco 提供的答案不支持保留未定义的列表类型,例如{{ some_list[4] }},这是我需要的。下面的解决方案解决了这个问题,以及所有可能的架构类型。

    这个想法是解析输入模板并尝试使用提供的上下文解析每个表达式。任何无法解析的,我们都替换为一个简单地解析为字符串的原始表达式的双花括号表达式。

    在调用渲染之前通过下面的preserve_undefineds_in_template 函数传递你的模板和上下文:

    from jinja2 import Template, StrictUndefined, UndefinedError
    import re
    
    def preserve_undefineds_in_template(template, context):
        patt = re.compile(r'(\{\{[^\{]*\}\})')
        j2_expressions = patt.findall(template)
        for j2_expression in set(j2_expressions):
            try:
                Template(j2_expression, undefined=StrictUndefined).render(context)
            except UndefinedError:
                template = template.replace(j2_expression, f"{{% raw %}}{j2_expression}{{% endraw %}}")
        return template
    

    例子:

    template = """hello {{ name }}, {{ preserve_me }} {{ preserve.me[2] }}"""
    
    context = { "name": "Alice" }
    
    # pass it through the preserver function
    template = preserve_undefineds_in_template(template, context)
    
    # template is now:
    # hello {{ name }}, {% raw %}{{ preserve.me }}{% endraw %} {% raw %}{{ preserve.me.too[0] }}{% endraw %}
    
    # render the new template as normal
    result = Template(template).render(context)
    
    print(result)
    

    输出是:

    hello Alice, {{ preserve_me }} {{ preserve.me[2] }}
    

    【讨论】:

      【解决方案3】:

      这是带有Template 而不是Environment 的版本:

      from jinja2 import Template, DebugUndefined
      
      template = Template("<p>{{name}} has {{quantity}}</p>", undefined=DebugUndefined)
      
      new_template = Template(template.render(name="Test"))
      

      谢谢@giwyni

      如果你只有几个变量,另一个小技巧:

      <p>{{name}} has {{ "{{quantity}}" }}</p>
      

      第二个替换将替换为{{quantity}},所以一切都很好;)

      【讨论】:

        【解决方案4】:

        使用default built-in filter 可以实现。

        <p>{{name|default('{{name}}')}} has {{quantity|default('{{quantity}}')}}</p>
        

        缺点是代码变丑,变量名重复,降低了可维护性。

        【讨论】:

          【解决方案5】:

          我也看到了同样的行为。库 jinja2schema 提供了模板所需的变量架构。

          我的解决方案的步骤是:

          • 有一个模板
          • 获取架构结构
          • 填写一些数据
          • 用原始字符串填充缺失项的完整数据
          from jinja2 import Template
          import jinja2schema
          
          def assign(schema, data, root=''):
              '''Returns a corrected data with untouched missing fields
          
              '''
          
              out = {}
              for key in schema.keys():
          
                  if isinstance(schema[key], (str, jinja2schema.model.Scalar)): 
                      try:
                          out[key] = data[key]
                      except:
                          out[key] = f'{{{{ {root+key} }}}}'
          
                  elif isinstance(schema[key], (dict, jinja2schema.model.Dictionary)):
                      out[key]={}
                      try:
                          data[key]
                      except:
                          data[key] = {}
                      out[key] = assign(schema[key], data[key], root+key+'.')
          
              return out
          
          # original template
          template_str = '<p>{{name}} has {{quantity}}</p>'
          # read schema
          schema = jinja2schema.infer(template_str)
          # available data
          data = {'name':'test'}
          # data autocompleted
          data_corrected = assign(schema, data)
          # render
          template = Template(template_str)
          print(template.render(data_corrected))
          

          输出是

          <p>test has {{ quantity }}</p>
          

          这是预期的结果。

          希望它会有所帮助。此解决方案不适用于列表,但我认为可以扩展解决方案。如果需要,您还可以获取缺失字段的列表。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-02-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-06-11
            • 1970-01-01
            相关资源
            最近更新 更多