Maybe its me, but "Explained Visually" sounds more like it would have some 'visual' aspect to it besides a GIF of code (don't get me wrong, I think most code should be made an image to limit copy/pasting).
A website like VisuAlgo (http://visualgo.net/) is a great example of exampling something visually should mean
I agree. I was using "List Comprehensions: Now In Color" as a working title but I thought that was too nonsensical. It was actually the colorized code and not the gifs in particular that made me think "visual".
Coming from Ruby to Python...I could not believe how damn hard it was to comprehend the beautifully explained and simple code (just 21 lines!) in Peter Norvig's writeup of how to create a spelling corrector [1], purely because of the list comprehensions (and a few other visual differences, such as partial list notation)
For example:
inserts = [a + c + b for a, b in splits for c in alphabet]
It's no different (conceptually, at least) than doing:
inserts = []
for a, b in splits:
for c in alphabet:
inserts.append(a + c + b)
Or in Ruby (OK, maybe this isn't perfect Ruby):
inserts = splits.reduce([]) do |x, (a, b)|
alphabet.each{|c| x << a + c + b }
x
end
But the list comprehension was so difficult for me to read that it might as well have been a brand new concept. But I found I got quickly over it after rewriting all of his comprehensions as for-loops, and then trying to convert them back into comprehensions. It was a good reminder that sometimes the barrier is just plain muscle/memory reflex (and probably much more so for beginning programmers...)
Depends on a programmer I guess. I'm pretty confused by the Ruby version and I can do Ruby in anger. I'd also use maps rather than each if I was writing it.
In the opposite direction, having knowledge of Python's list comprehensions (and to some degree, an understanding of lazy evaluation) helped me a ton when set-builder notation was introduced in my first discrete math class.
Are Python List Comprehensions really hard for people? I feel like my memory of learning them was I saw one in production code I was modifying, intuited what it did, pulled open a REPL and played with it for a bit, and then moved on.
I agree. It really surprised me to see this getting so much traffic. I sometimes get hung up with nested comprehensions when I haven't been using them in a while, but other than that, they are quite mundane.
That translation is kind of why i don't like python list comprehensions though. It's often clearer as a for-loop or map/filter, cause the different predicates are broken out into separate lines, rather than lumped together in one long line.
It is not much easier to understand the explicit loop:
doubled_odds = []
for n in numbers:
if n % 2 == 1:
doubled_odds.append(n * 2)
compared to the list comprehension:
doubled_odds = [n * 2 for n in numbers if n % 2 == 1]
Here's the explicit loop without newlines for comparison:
for n in numbers: if n % 2 == 1: doubled_odds.append(n * 2)
Notice that the for-loop and the if-statement are in the same order in the loop and in the list comprehension. Only the value expression (n * 2) is out-of-order. OPs jumping around is often unnecessary in the gif.
The one line list comprehension is as much readable for people familiar with Python as the variant with the explicit loop.
It is not uncommon to use several list comprehensions in a function. If you rewrite them as explicit loops then it may increase the line count 4 times e.g., it can convert a simple 3-lines function that could be understood at a glance into 12 lines function that you have to read line by line to understand -- it is easy to keep track of 3 values on 3 lines but it is not clear whether it is true for 12 lines.
A good reason to use list comprehension for simple things though is that you can sometimes quickly change [...] to (...) and get an equivalent generator. With a loop, you'd have to extract it into a separate yielding function.
I'm a big fan of just using map/filter, but I think it makes sense to wonder whether such a common and important operation is worth its own syntax. I do think the way nesting works is a bit surprising. It didn't really occur to me until reading an earlier comment that in essence the most nested loop is a `map` and all the others are `flatMap`s. (I think?)
Perhaps a for-loop is more similar to your memory of other code, but a list comprehension is more similar to an English sentence.
"All names longer than 4 characters in the list of cities?"
all(len(name) > 4 for name in cities)
"Sum of x-squared for x from 10 to 20."
sum(x**2 for x in range(10, 20))
Frankly, comprehensions are one of Python's worst features. Some properly implemented functional syntax would be both more readable and concise. Plus double-nested (I'm not sure what you'd call it) comrpehensions seem to be backwards. Rather than [ingredient for ingredient in recipe for recipe in cookbook] it's [ingredient for recipe in cookbook for ingredient in recipe] which makes it sound as if there's only going len(recipe) number of ingredients in your list. Plus it's so verbose and horizontal, half the time if you want descriptive names you're gonna write something so verbose it doesn't even make it that much more readable.
When reading code I find comprehensions easier to comprehend than combinations of map, filter and reduce. That is considering that I played around with some Common-Lisp before getting into Python.
The parts in the comprehension
[*wanted* *iteration(s?)* *condition?*]
compares well to
*iterations(s?)*
*condition?*
*append wanted*
for normal looping behaviour without changing order of the iterations.
One of my favourite comprehensions is of the sort
[item for item in items in for i in (0, 1)]
of course this can be the same as
list(items) * 2
but the comprehension is more versatile
[item for item in items in for i in (0, 1) if items[0] != 'a']
For most once the mental fog clears comprehensions become natural very quickly as they are not that far removed from normal loops.
Haskell, Erlang, and Clojure all have sane lambdas (assuming that's what you mean by "properly implemented functional syntax"), and list comprehensions are useful and idiomatic in those languages. Agreed about sequence names feeling out-of-order in nested comprehensions in Python.
From the Zen of Python:
> There should be one-- and preferably only one --obvious way to do it.
If you have something written as a simple for loop, there's a real danger that you're moving in the wrong direction by translating it into a list comprehension.
> Plus double-nested (I'm not sure what you'd call it) comrpehensions seem to be backwards.
Why are you even doing this? This isn't an acceptable way to write code.
> Plus it's so verbose and horizontal, half the time if you want descriptive names you're gonna write something so verbose it doesn't even make it that much more readable.
If your list comprehension is more than 80 characters wide, it's not because list comprehensions are bad, it's because you did something too complicated. Pull it into a function and name it so people know what the hell you're doing.
> Sometimes a programming design pattern becomes common enough to warrant its own special syntax. Python’s list comprehensions are a prime example of such a syntactic sugar.
I disagree with this thesis, although the post is otherwise a very nice guide to list comprehensions. Special syntax for specific types indicates a failure of the language to be generic enough.
List comprehensions anoint lists as a special thing. You don't get to play with list comprehensions, unless you're using the type that the language has decided to let you play with. If you decide to use a different type for some reason, you... can't.
map and filter can be part of a typeclass/interface/protocol (in Python these would just be informal), so you can use them on arbitrary types. If you want to switch your list type, it should just work.
I write a lot of Swift, and I'm constantly frustrated that optional chaining (?.) and exceptions are special things. I can't implement ?. for my type (say, a Result). Only the language creators can use it. Somewhat confusingly, ?? is a thing I can implement.
In Python's case, I think that list comprehensions are necessary because the language's support for first-class closures is poor.
> You don't get to play with list comprehensions, unless you're using the type that the language has decided to let you play with. If you decide to use a different type for some reason, you... can't.
You can use generator expressions and pass it to your custom type's constructor.
> List comprehensions anoint lists as a special thing. You don't get to play with list comprehensions, unless you're using the type that the language has decided to let you play with.
That's one reason why I like Scala's comprehensions; they have the conciseness of list comprehensions, but are more generic.
One basic misstep here for me is a comparison of why list comprehensions are better than loops -- speed, ease of reading, etc. There are a lot of comparisons, but no discussion of WHY list comprehensions.
I work on a project that's written mostly in Python; on occasion, other coworkers have to edit/work with my code and they're not used to Python at all, so I find myself writing for loops instead of list comprehensions for clarity.
I find the utility of list comprehensions is maximized when I'm in a Python shell and dealing with indentation when writing a loop can become obnoxious to edit. I tend to have a Python shell tab open in my terminal throughout the day, and it's nice to bang out a quick loop in a one-liner.
When writing actual code, I tend to stick with regular for loops.
I think animating the transformation from a normal loop to a list comprehension is a great way to show how the syntax translates between the two forms. Very awesome and comprehensive post.
Maybe I'm an FP snob but it would have been nice to mention the analogy to set builder notation (though lists aren't sets - maybe multisets or bags) to indicate the existence of higher abstraction; not just syntactic sugar on the for-loop.
However, from a practical point of view I think it is great introduction for procedural programmers to begin using list comprehensions.
nested comprehensions are kind of a nightmare to read, but basic list comprehensions seem incredibly straightforward to me. So much so that the explanation is more complex.
[+] [-] tsumnia|10 years ago|reply
A website like VisuAlgo (http://visualgo.net/) is a great example of exampling something visually should mean
[+] [-] th|10 years ago|reply
Sorry for the misleading title!
[+] [-] scrollaway|10 years ago|reply
You think what now?
[+] [-] ttkeil|10 years ago|reply
[+] [-] unknown|10 years ago|reply
[deleted]
[+] [-] x1798DE|10 years ago|reply
[+] [-] birchb|10 years ago|reply
[+] [-] danso|10 years ago|reply
For example:
It's no different (conceptually, at least) than doing: Or in Ruby (OK, maybe this isn't perfect Ruby): But the list comprehension was so difficult for me to read that it might as well have been a brand new concept. But I found I got quickly over it after rewriting all of his comprehensions as for-loops, and then trying to convert them back into comprehensions. It was a good reminder that sometimes the barrier is just plain muscle/memory reflex (and probably much more so for beginning programmers...)[1] http://norvig.com/spell-correct.html
[+] [-] mjevans|10 years ago|reply
[+] [-] akavi|10 years ago|reply
[+] [-] viraptor|10 years ago|reply
[+] [-] chestervonwinch|10 years ago|reply
https://en.wikipedia.org/wiki/Set-builder_notation
[+] [-] Gnewt|10 years ago|reply
[+] [-] th|10 years ago|reply
[+] [-] iamsohungry|10 years ago|reply
[+] [-] brbsix|10 years ago|reply
[+] [-] coherentpony|10 years ago|reply
[+] [-] bgilroy26|10 years ago|reply
[+] [-] unknown|10 years ago|reply
[deleted]
[+] [-] ebola1717|10 years ago|reply
[+] [-] d0mine|10 years ago|reply
The one line list comprehension is as much readable for people familiar with Python as the variant with the explicit loop.
It is not uncommon to use several list comprehensions in a function. If you rewrite them as explicit loops then it may increase the line count 4 times e.g., it can convert a simple 3-lines function that could be understood at a glance into 12 lines function that you have to read line by line to understand -- it is easy to keep track of 3 values on 3 lines but it is not clear whether it is true for 12 lines.
[+] [-] viraptor|10 years ago|reply
[+] [-] sanderjd|10 years ago|reply
[+] [-] msellout|10 years ago|reply
[+] [-] c3534l|10 years ago|reply
[+] [-] has2k1|10 years ago|reply
The parts in the comprehension
compares well to for normal looping behaviour without changing order of the iterations.One of my favourite comprehensions is of the sort
of course this can be the same as but the comprehension is more versatile For most once the mental fog clears comprehensions become natural very quickly as they are not that far removed from normal loops.[+] [-] oinksoft|10 years ago|reply
[+] [-] darkerside|10 years ago|reply
If you have something written as a simple for loop, there's a real danger that you're moving in the wrong direction by translating it into a list comprehension.
[+] [-] iamsohungry|10 years ago|reply
Why are you even doing this? This isn't an acceptable way to write code.
> Plus it's so verbose and horizontal, half the time if you want descriptive names you're gonna write something so verbose it doesn't even make it that much more readable.
If your list comprehension is more than 80 characters wide, it's not because list comprehensions are bad, it's because you did something too complicated. Pull it into a function and name it so people know what the hell you're doing.
[+] [-] cballard|10 years ago|reply
I disagree with this thesis, although the post is otherwise a very nice guide to list comprehensions. Special syntax for specific types indicates a failure of the language to be generic enough.
List comprehensions anoint lists as a special thing. You don't get to play with list comprehensions, unless you're using the type that the language has decided to let you play with. If you decide to use a different type for some reason, you... can't.
map and filter can be part of a typeclass/interface/protocol (in Python these would just be informal), so you can use them on arbitrary types. If you want to switch your list type, it should just work.
I write a lot of Swift, and I'm constantly frustrated that optional chaining (?.) and exceptions are special things. I can't implement ?. for my type (say, a Result). Only the language creators can use it. Somewhat confusingly, ?? is a thing I can implement.
In Python's case, I think that list comprehensions are necessary because the language's support for first-class closures is poor.
[+] [-] leni536|10 years ago|reply
You can use generator expressions and pass it to your custom type's constructor.
https://www.python.org/dev/peps/pep-0289/
[+] [-] dragonwriter|10 years ago|reply
That's one reason why I like Scala's comprehensions; they have the conciseness of list comprehensions, but are more generic.
[+] [-] sanderjd|10 years ago|reply
[+] [-] sdrothrock|10 years ago|reply
I work on a project that's written mostly in Python; on occasion, other coworkers have to edit/work with my code and they're not used to Python at all, so I find myself writing for loops instead of list comprehensions for clarity.
[+] [-] TheCowboy|10 years ago|reply
When writing actual code, I tend to stick with regular for loops.
[+] [-] tsurantino|10 years ago|reply
I think animating the transformation from a normal loop to a list comprehension is a great way to show how the syntax translates between the two forms. Very awesome and comprehensive post.
[+] [-] hatmatrix|10 years ago|reply
However, from a practical point of view I think it is great introduction for procedural programmers to begin using list comprehensions.
[+] [-] keepitsurreal|10 years ago|reply
[+] [-] jredwards|10 years ago|reply
[+] [-] skadamat|10 years ago|reply
[+] [-] y04nn|10 years ago|reply
[+] [-] plusquamperfekt|10 years ago|reply
[+] [-] unknown|10 years ago|reply
[deleted]