一个 Jinja 变量的作用域是否可以超出内部块?

74

我有以下Jinja模板:

{% set mybool = False %}
{% for thing in things %}
    <div class='indent1'>
        <ul>
            {% if current_user %}
              {% if current_user.username == thing['created_by']['username'] %}
                {% set mybool = True %}
                <li>mybool: {{ mybool }}</li> <!-- prints True -->
                <li><a href='#'>Edit</a></li>
              {% endif %}
            {% endif %}
            <li>Flag</li>
        </ul>
    </div>
    <hr />
{% endfor %}

{% if not mybool %}
    <!-- always prints this -->
    <p>mybool is false!</p>
{% else %}
  <p>mybool is true!</p>
{% endif %}

如果在for循环中满足条件,我想将mybool更改为true,以便我可以在下面显示mybool is true!。但是,内部mybool的作用域似乎仅限于if语句,因此从未设置所需的mybool
如何设置“全局”mybool,以便我可以在最后的if语句中使用它? 编辑 我发现了一些建议(只有缓存的页面正确),但它们似乎不起作用。也许它们在Jinja2中已过时... 编辑 下面提供解决方案。尽管如此,我仍然好奇上述建议为什么不起作用。有人确定它们已过时吗?

1
这并不回答你的问题,但是你可以将 mybool 设置为上下文变量,并将其传递到模板中。 - Cameron
那是很好的想法,但不幸的是它不起作用。一旦你在模板中使用“set”,该变量的作用域就是局部的。 - Matt Norris
2
以下是提供的解决方案。虽然上面的建议不起作用,但我仍然很好奇为什么。有人确定它们已被弃用吗?它们被删除是因为在生成的代码中无法正确预测它们在Python堆栈上需要向上冒泡多远。虽然可以通过一些技巧实现,但这并不值得花费这样的努力。将逻辑保持在模板之外 :) - Armin Ronacher
8个回答

56

绕过这种限制的一种方法是启用"do"表达式语句扩展并使用数组代替布尔值:

{% set exists = [] %}
{% for i in range(5) %}
      {% if True %}
          {% do exists.append(1) %}
      {% endif %}
{% endfor %}
{% if exists %}
    <!-- exists is true -->
{% endif %}

为启用Jinja的“do”表达式语句扩展:e = jinja2.Environment(extensions=["jinja2.ext.do",])


26
不需要这个"do"表达式语句。将表达式放入if条件中 :) {% if exists.append(1) %}{% endif %} - schettino72
1
我需要实际修改全局变量,最终得到了一个数组: {% set platform = [] %}读取“全局”变量: {% platform[-1] %}“更改”“全局”变量: {% if platform.append(new_platform) %}{% endif %} - Arie Skliarouk
14
当你没有do表达式时,甚至有一种更短的方法可以实现这一点:{% set _ = exists.append(1) %}。这在DebOps项目(Ansible)中是常见做法。 - ypid

22

对于任何想要使用namespace()对象在for循环外保持变量的一般情况如下。

{% set accumulator = namespace(total=0) %}
{% for i in range(0,3) %}
    {% set accumulator.total = i + accumulator.total %}
    {{accumulator.total}}
 {% endfor %}`          {# 0 1 3 #}
 {{accumulator.total}}  {# 3 (accumulator.total persisted past the end of the loop) #}

18

回答相关问题:我想要一个全局计数器,用于统计我进入模板中某个 if-block 的次数,最终得到了下面的代码。

在模板顶部:

{% set counter = ['1'] %}

在 if 块中我想要计数:

{% if counter.append('1') %}{% endif %}
显示计数时:
{{ counter|length }}
字符串'1'可以被替换为任何字符串或数字,我相信。这仍然是一种hack,但不是很大的一个。

17

2018年更新

从Jinja 2.10版本开始(2017年11月8日),引入了namespace()对象, 以解决特定问题。详见Jinja官方的Assignments文档并查看示例;然后,class文档说明如何将多个值分配到一个命名空间(namespace)。


我在这里发布了一个使用namespace()对象的模板 链接 - jeffmjack
1
谢谢回答 - 如果它包含一个简单的例子,我会点赞的。 - user2682863
@user2682863,所引用的文档包含示例。 - Jens
2
@jens 是的,但是提供一个简单的例子会让这个答案更好,并确保在链接失效时仍然有用。 - user2682863

8
您可以使用此“hack”(无需扩展程序)解决您的问题:
import jinja2

env = jinja2.Environment()
print env.from_string("""
{% set mybool = [False] %}
{% for thing in things %}
    <div class='indent1'>
        <ul>
            {% if current_user %}
              {% if current_user.username == thing['created_by']['username'] %}
                {% set _ = mybool.append(not mybool.pop()) %}
                <li>mybool: {{ mybool[0] }}</li> <!-- prints True -->
                <li><a href='#'>Edit</a></li>
              {% endif %}
            {% endif %}
            <li>Flag</li>
        </ul>
    </div>
    <hr />
{% endfor %}

{% if not mybool[0] %}
    <!-- always prints this -->
    <p>mybool is false!</p>
{% else %}
  <p>mybool is true!</p>
{% endif %}
""").render(current_user={'username':'me'},things=[{'created_by':{'username':'me'}},{'created_by':{'username':'you'}}])

4

当编写contextfunction()或类似函数时,您可能已经注意到上下文会阻止您进行修改。

如果您使用内部上下文API成功修改了上下文,您可能已经注意到上下文中的更改似乎在模板中不可见。原因是为了提高性能,Jinja仅将上下文用作模板变量的主要数据源。

如果您想修改上下文,请编写一个返回变量的函数,然后使用set将其分配给变量:

{% set comments = get_latest_comments() %}

Source


4

我需要找出一个列表(objects_from_db)中每个对象(object)的最大条目数。

由于Jinja2和变量作用域的限制,原来的方法并不奏效。

 {% set maxlength = 0 %}
 {% for object in objects_from_db %}
     {% set ilen = object.entries | length %}
     {% if maxlength < ilen %}
         {% set maxlength = ilen %}
     {% endif %}
 {% endfor %}

以下是可行的方案:

 {% set mlength = [0]%}
 {% for object in objects_from_db %}
     {% set ilen = object.entries | length %}
     {% if mlength[0] < ilen %}
         {% set _ = mlength.pop() %}
         {% set _ = mlength.append(ilen)%}
     {% endif %}
 {% endfor %}
 {% set maxlength = mlength[0] %}

希望这能帮助其他尝试解决相同问题的人。

0
发现了一篇很棒的文章,介绍了一个小技巧。在不同的作用域中无法更改jinja变量的值,但可以修改全局字典的值:
# works because dictionary pointer cannot change, but entries can 

{% set users = ['alice','bob','eve'] %} 
{% set foundUser = { 'flag': False } %} 

initial-check-on-global-foundUser: 
  cmd.run: 
    name: echo initial foundUser = {{foundUser.flag}} 

{% for user in users %} 
{%- if user == "bob" %} 
{%-   if foundUser.update({'flag':True}) %}{%- endif %} 
{%- endif %} 
echo-for-{{user}}: 
  cmd.run: 
    name: echo my name is {{user}}, has bob been found? {{foundUser.flag}} 
{% endfor %} 

final-check-on-global-foundUser: 
  cmd.run: 
    name: echo final foundUser = {{foundUser.flag}}

我也发现使用以下语法设置值非常有帮助,而不必实际使用set

{%-   if foundUser.update({'flag':True}) %}{%- endif %} 

它实际上检查了对字典执行update操作的结果(自己注意)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接