1

I have an api call that returns a dictionary with all the ids for all users and others to get the userDetails like:

>>> allIds=api.getAllIds()
{1,2,3,4,5}
>>> userDetails=api.getUserDetails(1)
{'name':'Bob','age':'19'}

I'm trying to pack the whole structure in a dict with the userId-like key and the info-like value.

I could make it work with:

>>> users={id:api.getUserDetails(id) for id in allIds}
{1:{'name':'bob','age':'19'},2:None,3:{'name':'alice','age':'21'},...}

The problem is that not all Ids returns a response as you can see for the id 2 call. I'm trying not to use traditional loops and I'm solving the situation cleaning the resulting dict after the calls with:

>>> users=[id:users[id] for id in users.keys() if users[id]!=None]

It's working, but I'm trying not to put inside the empty values instead of cleaning. In a normal situation you could create the dict like:

>>> a=[id:b[id] for id in b.keys() if b[id]!=None ]

But in my case if I check if b[i] is None... then I'm calling the api two times because my b[i] expression is api.getUserDetails(id), one to take the value and the other to check if is not None.

So I'm trying something like nested dictionary comprehension:

users = {(x,y) for x in usersIds for y in api.getUserDetails(x) if y!=None }

But I can not make it work.

Anyone know how to do it?

martineau
  • 119,623
  • 25
  • 170
  • 301
Juan
  • 13
  • 3
  • 1
    Compare against None with `is`. And `users={id:api.getUserDetails(id) for id in allIds if api.getUserDetails(id) is not None}` should work – Maarten Fabré Jun 08 '17 at 16:00
  • @MaartenFabré He said he wants to avoid calling `api.getUserDetails(id)` twice: _"But in my case if I check if b[i] is None... then I'm calling the api two times[...]."_. – Christian Dean Jun 08 '17 at 16:01
  • since you cannot assign inside a comprehension, You would either have to make a sencond, cleaning comprehension `users = {key: value for key, value in users.items() if value is not None}`, work with a real for-loop, or with an external generator function – Maarten Fabré Jun 08 '17 at 16:05
  • `{(x,y) for x in usersIds for y in api.getUserDetails(x) if y!=None }` here you are making an inner, nested loop over the userdetails, so this will not work indeed – Maarten Fabré Jun 08 '17 at 16:06

1 Answers1

4

You can put the result of the api call in a singleton tuple:

users = {x: y for x in usersIds for y in (api.getUserDetails(x),) if y is not None}

Demo:

lst = [(5,6), (2,None)]
d = {k: v for k, x in lst for v in (x,) if v is not None}
print(d)
# {5: 6}

However, it is pretty wasteful to set up a nested loop for such and may be more readable to use a vanilla for loop to build the dictionary:

users = {}
for x in usersIds:
    y = api.getUserDetails(x)
    if y is not None:
        users[x] = y
Moses Koledoye
  • 77,341
  • 8
  • 133
  • 139
  • 1
    Dang it, was just about to post the same thing. Beat me to ;) +1 – Christian Dean Jun 08 '17 at 16:10
  • Very interesting. What do you mean by wasteful? In terms of performances? Comprehension are usually faster than loops as far as I know, it the insertion inside of a tuple so expensive? – Ando Jurai Jul 12 '17 at 11:48
  • 1
    @AndoJurai The nested *for* only serves to persist the result of the `api` call, just like an assignment. It does not serve any other purpose. Notice how a tuple is being created with a single element. – Moses Koledoye Jul 12 '17 at 12:03
  • So it builds a tuple and initiate a useless for loop of one element each time. I got the tuple creation but was not sure it was expensive, but the useless additional loop might indeed be. – Ando Jurai Jul 12 '17 at 12:21