zenprogrammer
zenprogrammer

Reputation: 711

How to make nested for loop more Pythonic

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

Answers (4)

MSeifert
MSeifert

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.names and key.names are hashable.

Upvotes: 4

timgeb
timgeb

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

Bubble Bubble Bubble Gut
Bubble Bubble Bubble Gut

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

MSeifert
MSeifert

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

Related Questions