Reputation: 711
I have to create a list of blocked users per key. Each user has multiple attributes and if any of these attributes are in keys, the user is blocked.
I wrote the following nested for
-loop and it works for me, but I want to write it in a more pythonic way with fewer lines and more readable fashion. How can I do that?
for key in keys:
key.blocked_users = []
for user in get_users():
for attribute in user.attributes:
for key in keys:
if attribute.name == key.name:
key.blocked_users.append(user)
Upvotes: 12
Views: 4690
Reputation: 152607
Aside from making it shorter, you could try to reduce the operations to functions that are optimized in Python. It may not be shorter but it could be faster then - and what's more pythonic than speed?. :)
For example you iterate over the keys for each attribute of each user. That just sreams to be optimized "away". For example you could collect the key-names in a dictionary (for the lookup) and a set (for the intersection with attribute names) once:
for key in keys:
key.blocked_users = []
keyname_map = {key.name: key.blocked_users for key in keys} # map the key name to blocked_user list
keynames = set(keyname_map)
The set(keyname_map)
is a very efficient operation so it doesn't matter much that you keep two collections around.
And then use set.intersection
to get the keynames that match an attribute name:
for user in get_users():
for key in keynames.intersection({attribute.name for attribute in user.attributes}):
keyname_map[key].append(user)
set.intersection
is pretty fast too.
However, this approach requires that your attribute.name
s and key.name
s are hashable.
Upvotes: 4
Reputation: 78650
In your specific case, where the inner for
loops rely on the outer loop variables, I'd leave the code just as is. You don't make code more pythonic or readable by forcefully reducing the number of lines.
If those nested loops were intuitively written, they are probably easy to read.
If you have nested for
loops with "independent" loop variables, you can use itertools.product
however. Here's a demo:
>>> from itertools import product
>>> a = [1, 2]
>>> b = [3, 4]
>>> c = [5]
>>> for x in product(a, b, c): x
...
(1, 3, 5)
(1, 4, 5)
(2, 3, 5)
(2, 4, 5)
Upvotes: 7
Reputation: 3358
Try using listed for loop in list comprehension, if that's considered more Pythonic, something like:
[key.blocked_users.append(user) for key in keys
for attribute in user.attributes
for user in get_users()
if attribute.name == key.name]
Upvotes: -1
Reputation: 152607
You could use a conditional comprehension in your first for
-loop:
for key in keys:
keyname = key.name
key.blocked_users = [user for user in get_users() if any(attribute.name == keyname for attribute in user)]
Upvotes: 5