Python Dict inside List inside Dict - comprehension possible?

Viewed 43

I have an object in Python 3 of this format:

a = {
        'events': [
            {
                'timestamp': 123,
                'message': 'test'
            },
            {
                'timestamp': 456,
                'message': 'foo'
            },
            {
                'timestamp': 789,
                'message': 'testbar'
            },
        ],
        'first': 'abc',
        'last': 'def'
    }

I want to create a new object of the same format, but filtered by whether the message key's corresponding value contains a certain string, for example filtering by "test":

a = {
        'events': [
            {
                'timestamp': 123,
                'message': 'test'
            },
            {
                'timestamp': 789,
                'message': 'testbar'
            },
        ],
        'first': 'abc',
        'last': 'def'
    }

Can I use a nested comprehension for this? I know you can do nested list comprehensions like:

[[y*2 for y in x] for x in l]

But is there a neat way for a dict > list > dict situation?

3 Answers

One option would be to create a new copy of the input dict without events, and then set the filtered events as you require, like this:

copy = {k: v for k, v in a.items() if k != 'events'}
copy['events'] = [e for e in a['events'] if 'test' in e['message']]

Or if you don't mind overwriting the original input, simply do this:

a['events'] = [e for e in a['events'] if 'test' in e['message']]

I would go with a list comprehension with an if-statement like the following:

[event for event in a["events"] if event["message"] == "test" ]

Loop through the values of the "events"-key and add them to the list if the value of their "message" key equals "test".

The result is a list of dictionaries that you can assign back to a["events"] or a copy of a if you would like to preserve a["events"].

So - you can use multiple layers of comprehension, but that doesn't mean you should. I think for such an example, you'd produce cleaner code, by running it through a couple of for loops. Having that said, I think the following is technically achieves the outcome you're asking for.

>>> pprint.pprint(a) 
{'events': [{'message': 'test', 'timestamp': 123},
            {'message': 'foo', 'timestamp': 456},
            {'message': 'testbar', 'timestamp': 789}],
 'first': 'abc',
 'last': 'def'}
>>> aa = copy.deepcopy(a)
>>> aa['beta'] = aa['events']
>>> pprint.pprint({k:[item for item in v if 'test' in item['message']] if isinstance(v, list) else v for k, v in aa.items()})
{'beta': [{'message': 'test', 'timestamp': 123},
          {'message': 'testbar', 'timestamp': 789}],
 'events': [{'message': 'test', 'timestamp': 123},
            {'message': 'testbar', 'timestamp': 789}],
 'first': 'abc',
 'last': 'def'}
>>> pprint.pprint({k:[item for item in v if 'test' in item['message']] if isinstance(v, list) else v for k, v in a.items()})
{'events': [{'message': 'test', 'timestamp': 123},
            {'message': 'testbar', 'timestamp': 789}],
 'first': 'abc',
 'last': 'def'}

As said, this is something you can do; I would however on behalf of everyone who's had to read other people code in their careers, respectfully request that you don't use this in production code. A couple of for loops might be more LOC, but would in most cases be much more readable and maintainable.

Related