Use case for nested/multiple list comprehensions or generator expressions. When is it more elegant?
Asked Answered
G

7

16

I see this kind of thing sometimes:

(k for k in (j for j in (i for i in xrange(10))))

Now this really bends my brain, and I would rather it wasn't presented in this way.

Are there any use-cases, or examples of having used these nested expressions where it was more elegant and more readable than if it had been a nested loop?

Edit: Thanks for the examples of ways to simplify this. It's not actually what I asked for, I was wondering if there were any times when it was elegant.

Grillparzer answered 15/3, 2009 at 22:22 Comment(3)
"Sometimes"? Really? What are you reading?Nakitanalani
Code snippets, open-source projects. That would get code-reviewed out of anything I had ever managed.Grillparzer
Where have you seen such a thing? Can you provide a specific URL?Nakitanalani
F
22

Check PEP 202 which was where list comprehensions syntax was introduced to the language.

For understanding your example, there is a simple rule from Guido himself:

  • The form [... for x... for y...] nests, with the last index varying fastest, just like nested for loops.

Also from PEP 202, which serves to answer your question:

Rationale
    List comprehensions provide a more concise way to create lists in
    situations where map() and filter() and/or nested loops would
    currently be used.

If you had a situation like that, you could find it to be more elegant. IMHO, though, multiple nested list comprehensions may be less clear in your code than nested for loops, since for loops are easily parsed visually.

Feuillant answered 16/3, 2009 at 0:39 Comment(0)
L
17

If you're worried about too much complexity on one line, you could split it:

(k for k in 
    (j for j in 
        (i for i in xrange(10))))

I've always found line continuations to look a little weird in Python, but this does make it easier to see what each one is looping over. Since an extra assignment/lookup is not going to make or break anything, you could also write it like this:

gen1 = (i for i in xrange(10))
gen2 = (j for j in gen1)
gen3 = (k for k in gen2)

In practice, I don't think I've ever nested a comprehension more than 2-deep, and at that point it was still pretty easy to understand.

Lamm answered 15/3, 2009 at 22:41 Comment(0)
A
6

In the case of your example, I would probably write it as:

foos = (i for i in xrange(10))
bars = (j for j in foos)
bazs = (k for k in bars)

Given more descriptive names, I think this would probably be quite clear, and I can't imagine there being any measurable performance difference.

Perhaps you're thinking more of expressions like:

(x for x in xs for xs in ys for ys in lst)

-- actually, that's not even valid. You have to put things in the other order:

(x for ys in lst for xs in ys for x in xs)

I might write that as a quick way of flattening a list, but in general I think you're write: the time you save by typing less is usually balanced by the extra time you spend getting the generator expression right.

Aegrotat answered 15/3, 2009 at 22:41 Comment(0)
O
4

Since they are generator expressions, you can bind each to it's own name to make it more readable without any change in performance. Changing it to a nested loop would likely be detrimental to performance.

irange = (i for i in xrange(10))
jrange = (j for j in irange)
krange = (k for k in jrange)

It really doesn't matter which you choose, I think the multi-line example is more readable, in general.

Optime answered 15/3, 2009 at 22:40 Comment(0)
E
1

Caveat: elegance is partly a matter of taste.

List comprehensions are never more clear than the corresponding expanded for loop. For loops are also more powerful than list comprehensions. So why use them at all?

What list comprehensions are is concise -- they allow you to do something in a single line.

The time to use a list comprehension is when you need a certain list, it can be created on the fly fairly easily, and you don't want or need intermediate objects hanging around. This might happen when you need to package some objects in the current scope into a single object that you can feed into a function, like below:

list1 = ['foo', 'bar']
list2 = ['-ness', '-ity']
return filterRealWords([str1+str2 for str1 in list1 for str2 in list2])

This code is about as readable than the expanded version, but it is far shorter. It avoids creating/naming an object that is only used once in the current scope, which is arguably more elegant.

Equitable answered 24/1, 2016 at 22:38 Comment(0)
S
0

The expression:

(k for k in (j for j in (i for i in xrange(10))))

is equivalent to:

(i for i in xrange(10))

that is almost the same:

xrange(10)

The last variant is more elegant than the first one.

Significs answered 16/3, 2009 at 12:38 Comment(1)
I'm pretty sure this was just demonstration of what the OP meant by nested comprehensions, not an actual examplePlush
T
0

I find it can be useful and elegant in situations like these where you have code like this:

output = []
for item in list:
    if item >= 1:
        new = item += 1
        output.append(new)

You can make it a one-liner like this:

output = [item += 1 for item in list if item >= 1]
Tangerine answered 27/3, 2014 at 3:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.