Home > Software design >  Is a loop in a loop really the best way to output nested dict items in Python?
Is a loop in a loop really the best way to output nested dict items in Python?

Time:01-30

I have a dict from a json API call that looks like this:

{
  'id': '63d08d5c57abd98fdeea7985',
 'pageName': 'Some Page Name',
  'bodyContent':
    [{
      'children': [{
        'alpha': 'foo foo foo',
        'beta': 'bar bar bar'
        }]
    }],
 'date': '2023-01-25T02:01:00.965Z'
}

To reference the nested items inside bodyContent, I'm doing a loop within a loop (to get alpha):

{% for item in items.get('bodyContent') %}
  {% for i in item.get('children') %}
    {{ i['alpha'] }}
  {% endfor %}
{% endfor %}

I see these nested for loops in a lot of example code, but is this really the best way - a loop within a loop? I can't help but feel like it's a bit dirty and am used to other languages where a loop within a loop isn't necessary for a structure that is this basic.

Edit: What other languages am I referring to, where nested loops wouldn't be necessary? Things like array_map or array_reduce in PHP to simplify data structures (when you can't alter how they're stored, like the data in my example from an API). If nested loops are A-OK in Python then that's cool too. I'm just not sure what the common wisdom is with them in Python.

CodePudding user response:

The loop you show in your question isn't written in Python. It's written in the Jinja template language, which really isn't all that much like Python at all.

We can solve this by providing Jinja with some filters that provide more convenient access to nested data structures. For example, if we write a JMESPath filter like this:

import jmespath

def filter_jmespath(v, expr):
    return jmespath.search(expr, v)

And provide that to Jinja, we can reduce your template to:

{{ items|jmespath('bodyContent[*].children[*].alpha[]|[0]') }}

Given the following complete example:

import jinja2
import jmespath

items = {
    "id": "63d08d5c57abd98fdeea7985",
    "pageName": "Some Page Name",
    "bodyContent": [{"children": [{"alpha": "foo foo foo", "beta": "bar bar bar"}]}],
    "date": "2023-01-25T02:01:00.965Z",
}


def filter_jmespath(v, expr):
    return jmespath.search(expr, v)


env = jinja2.Environment()
env.filters["jmespath"] = filter_jmespath

template = env.from_string(
    """
{{ items|jmespath('bodyContent[*].children[*].alpha[]|[0]') }}
"""
)

print(template.render(items=items))

We get the following output:


foo foo foo

In Python, we might write something like this (this is entered in the python REPL and assumes that your example data is stored in the variable items):

>>> next(y['alpha']
... for x in items['bodyContent']
... for y in x['children'])
'foo foo foo'
  • Related