Setting variable in Jinja for loop doesn't persist between iterations

后端 未结 3 1018
既然无缘
既然无缘 2021-01-19 16:27

I want to loop over a list of objects and count how many objects meet a requirement. I based my code off other examples I\'d found, but it doesn\'t work, the count is always

相关标签:
3条回答
  • 2021-01-19 17:01

    Jinja 2.10 introduces the namespace object to handle assignment and comparison in loops.

    {% set ns = namespace(beds=0) %}
    {% for room in house %}
        {% if room.has_bed %}
            {% set ns.beds = ns.beds + 1 %}
        {% endif %}
    {% endfor %}
    {{ house.address }} has {{ ns.beds }} beds.
    

    Normally, set does not handle attributes, which is why the old answers mutate objects with methods instead. namespace has been special cased so setting attributes does work.

    The reason the original counter didn't work is because of Jinja's scope rules. Unlike Python, most blocks are new scopes. set always defines a local variable, except in this new special case of namespace.attribute.


    In this specific case, you can accomplish what you want with filters.

    {% set beds = house.rooms|selectattr('has_bed')|length %}
    {{ house.address }} has {{ beds }} beds.
    

    However, there are cases where storing information across scopes is needed. For example, if you wanted to output some information in addition to incrementing the counter, it would make sense to use a namespace.

    0 讨论(0)
  • 2021-01-19 17:16

    For Jinja 2.9, the scope behavior was fixed, invalidating code that worked in previous versions. The incremented value of count only lives within the scope of the loop. Their example involves setting variables, but the concept is the same:

    Please keep in mind that it is not possible to set variables inside a block and have them show up outside of it. This also applies to loops. The only exception to that rule are if statements which do not introduce a scope. As a result the following template is not going to do what you might expect:

    {% set iterated = false %}
    {% for item in seq %}
        {{ item }}
        {% set iterated = true %}
    {% endfor %}
    {% if not iterated %} did not iterate {% endif %}
    

    It is not possible with Jinja syntax to do this.

    You will need to do a hacky-ish workaround in order to track count across iterations. Set a list, append to it, then count its length.

    {% for house in city %}
        {% set room_count = [] %}
        {% for room in house %}
            {% if room.has_bed %}
                {% if room_count.append(1) %}{% endif %}
            {% endif %}
        {% endfor %}
        <div>{{ house.address }} has {{ room_count|length }} beds.</div>
    {% endfor %}
    
    0 讨论(0)
  • 2021-01-19 17:16

    For Jinja <= 2.8, the code you've shown does work. However, this was due to incorrect behavior in Jinja's scope rules, which was fixed in 2.9.

    {% for house in city %}
        {% set count = 0 %}
        {% for room in house %}
            {% if room.has_bed %}
                {% set count = count + 1 %}
            {% endif %}
        {% endfor %}
        <div>{{ house.address }} has {{ count }} beds.</div>
    {% endfor %}
    
    0 讨论(0)
提交回复
热议问题