8

I have a list of dictionary and a string. I want to add a selected attribute in each dictionary inside the list. I am wondering if this is possible using a one liner.

Here are my inputs:

saved_fields = "apple|cherry|banana".split('|')
fields = [
    {
        'name' : 'cherry'
    }, 
    {
        'name' : 'apple'
    }, 
    {
        'name' : 'orange'
    }
]

This is my expected output:

[
    {
        'name' : 'cherry',
        'selected' : True
    }, 
    {
        'name' : 'apple',
        'selected' : True
    }, 
    {
        'name' : 'orange',
        'selected' : False
    }
]

I tried this:

new_fields = [item [item['selected'] if item['name'] in saved_fields] for item in fields]
cs95
  • 379,657
  • 97
  • 704
  • 746
simple guy
  • 633
  • 1
  • 6
  • 19
  • Are you sure you copied your code correctly? That looks like it would be an error. – kubatucka Jul 07 '20 at 08:19
  • 1
    "I tried this" - and how did that go? What error or output did you get? How did you try to fix it? Some people (myself partially included) appreciate seeing an attempt just for the sake of it, but really the point should be to iterate upon it and break it down and debug it and maybe that leads you to asking a different question, or maybe it just leads you to the answer. – Bernhard Barker Jul 07 '20 at 17:50
  • 1
    Can you do it in multiple lines? Starting there and then converting that to a single line would likely be much easier than trying to come up with a single line directly. Although what's with the "in one line" questions? Trying to force something into one line just makes for unreadable code. – Bernhard Barker Jul 07 '20 at 17:55
  • A *"one line for-loop"* is a ***dict comprehension***, in your case. (Or *list comprehension*, or *set comprehension*). Please read about *dict comprehensions*. – smci Jul 07 '20 at 20:06

5 Answers5

15

I don't necessarily think "one line way" is the best way.

s = set(saved_fields)  # set lookup is more efficient 
for d in fields:
    d['status'] = d['name'] in s

fields
# [{'name': 'cherry', 'status': True},
#  {'name': 'apple', 'status': True},
#  {'name': 'orange', 'status': False}]

Simple. Explicit. Obvious.

This updates your dictionary in-place, which is better if you have a lot of records or other keys besides "name" and "status" that you haven't told us about.


If you insist on a one-liner, this is one preserves other keys:

[{**d, 'status': d['name'] in s} for d in fields]  
# [{'name': 'cherry', 'status': True},
#  {'name': 'apple', 'status': True},
#  {'name': 'orange', 'status': False}]

This is list comprehension syntax and creates a new list of dictionaries, leaving the original untouched.

The {**d, ...} portion is necessary to preserve keys that are not otherwise modified. I didn't see any other answers doing this, so thought it was worth calling out.

The extended unpacking syntax works for python3.5+ only, for older versions, change {**d, 'status': d['name'] in s} to dict(d, **{'status': d['name'] in s}).

cs95
  • 379,657
  • 97
  • 704
  • 746
  • You should explain to the the OP that this *"one line for-loop"* is a ***dict comprehension***. – smci Jul 07 '20 at 20:05
  • @smci It's not a dict comprehension though? It's a list comprehension which creates a dictionary each iteration. Although the focal point of my answer is that it isn't always necessary you use one. – cs95 Jul 07 '20 at 20:33
  • It is a dict comprehension. In fact it's a nested dict comprehension inside a list comprehension. But anyway instead of just posting code that solves this specific OP's issue, please add some general explanation. (Not just to help learning, also for search indexing, findability, finding dupe targets etc.) – smci Jul 07 '20 at 20:47
  • 1
    @smci no, a dict comprehension would look like `{key : value for something in something_else}`. `{**d, 'a': 'b', 'c': 'd'}` just creates a dictionary. – cs95 Jul 07 '20 at 20:52
  • The `{...}` expression is a dict comprehension. The `**d` unpacks a dict into a sequence of `key, value`. Then that sequence is combined with the extra key `'status': d['name'] in s`, and `d['name'] in s` is a boolean expression for value. The surrounding `[...]` is a list comprehension. Like I said, the code here needs heavy explaining for non-Python users, it's doing four different things at once. – smci Jul 07 '20 at 21:02
  • @smci Not sure I understand the complaints here. Firstly, I don't agree with `{**d}` being called a "dict comprehension", that's only creating a dict literal using [extended iterable unpacking](https://www.python.org/dev/peps/pep-3132/). Secondly, I've explained both approaches here pretty satisfactorily, so these nitpicks are honestly bizarre to me. – cs95 Jul 07 '20 at 21:05
  • These are valid improvement suggestions, not 'complaints' or 'nitpicks', please stay constructive. Again, ask someone who isn't a Python user to look at this code and infer its intent. It needs explanation. (Would you like me to propose a suggested edit in Pythonchatroom?) – smci Jul 07 '20 at 21:12
  • @smci Your comment came with a downvote (that doesn't necessarily mean I believe you did it or hold it against you, but it surprised me nonetheless). Yeah, thanks for the suggestions, but you're still mistaken about the dict comprehension. Also, your claim that the answer is not clear ... is your claim and I respect that. But the first answer is pretty clear in its intent and readability - it updates a dictionary in place inside a loop. The second answer is just a herring to satisfy the OP's "one line" requirement, like I said not the focal point of the answer. – cs95 Jul 07 '20 at 21:15
  • @smci If you see ways to improve the answer, feel free to edit! – cs95 Jul 07 '20 at 21:15
  • 1
    cs95: No, I didn't downvote it! Not so paranoid, my friend. I don't believe I've ever downvoted any of your excellent content on SO, well maybe only one answer once. And certainly not this one. – smci Jul 07 '20 at 21:32
  • 1
    *"The second answer is just a herring to satisfy the OP's "one line" requirement, like I said not the focal point of the answer"*. I actually prefer it! I like it a lot. Also, people coming from functional languages will like it too. – smci Jul 07 '20 at 21:32
  • @smci `{**x, y: z}` is not a dict comprehension; those are of the form `{x: y for u in z}`. See [the docs](https://docs.python.org/3/tutorial/datastructures.html?highlight=comprehension#list-comprehensions) and [the PEP](https://www.python.org/dev/peps/pep-0202/) (linking to list comprehensions, because dict comprehensions are pretty much defined as "see list comprehensions"). – marcelm Jul 07 '20 at 21:35
  • @marcelm: ok, but *"a dict created from an iterable which was the combination of extended iterable unpacking on a dict, and a list comprehension"* definitely needs some explaining, and will not be intelligible to new Python users, even ones experienced in other languages. – smci Jul 14 '20 at 12:20
5

You could update the dictionary with the selected key

for x in fields: x.update({'selected': x['name'] in saved_fields}):

print(fields)

[{'name': 'cherry', 'selected': True}, 
{'name': 'apple', 'selected': True}, 
{'name': 'orange', 'selected': False}]
renatodamas
  • 16,555
  • 8
  • 30
  • 51
ScootCork
  • 3,411
  • 12
  • 22
  • 5
    I strongly do not recommend using list comprehensions for side effects, this is a [well-known antipattern](https://stackoverflow.com/questions/5753597/is-it-pythonic-to-use-list-comprehensions-for-just-side-effects). You are generating a result of `[None, None, None]` and throwing it away. – cs95 Jul 07 '20 at 08:26
  • 1
    Good point, updated to different one liner as requested by OP. – ScootCork Jul 07 '20 at 08:35
4
result = [
    {"name": fruit['name'],
     "selected": fruit['name'] in saved_fields } 
    for fruit in fields
]

>>> [{'name': 'cherry', 'selected': True},
 {'name': 'apple', 'selected': True},
 {'name': 'orange', 'selected': False}]

And as a one-liner:

result = [{"name": fruit['name'], "selected": fruit['name'] in saved_fields} for fruit in fields]
borisdonchev
  • 1,114
  • 1
  • 7
  • 20
3

The proposed solutions will work even if there is more than one entry in the dicts.

Taking your inputs :

saved_fields = "apple|cherry|banana".split('|')
fields = [
    {
        'name' : 'cherry'
    }, 
    {
        'name' : 'apple'
    }, 
    {
        'name' : 'orange'
    }
]
  1. Using the Dict.update():

    >>> [item.update({'selected': item['name'] in saved_fields}) for item in fields]
    [None, None, None]
    

    Will return [None, None, None] but modifies the fields variable inplace.

    >>> fields
    [{'name': 'cherry', 'selected': True},
     {'name': 'apple', 'selected': True},
     {'name': 'orange', 'selected': False}]
    

    note that while this is a one-liner, this is not always recommended.

  2. If you want a new list without modifying fields. It can be done using ** operator on Dict cf as shown in @cs95 answer. ** explanation:

    >>> new_fields = [{**item, 'selected': item['name'] in saved_fields} for item in fields]
    >>> new_fields
    [{'name': 'cherry', 'selected': True},
     {'name': 'apple', 'selected': True},
     {'name': 'orange', 'selected': False}]
    
    >>> fields
    [{'name': 'cherry'}, {'name': 'apple'}, {'name': 'orange'}]
    
cs95
  • 379,657
  • 97
  • 704
  • 746
  • 3
    Using a list comprehension just for side effects is a Python [anti-pattern](https://stackoverflow.com/questions/5753597/is-it-pythonic-to-use-list-comprehensions-for-just-side-effects) – Barmar Jul 07 '20 at 17:24
2
[{'name': item['name'], 'selected': item['name'] in saved_fields} for item in fields]