4 ms·
Python list/dict comprehensions are awesome. The only other language I've seen that has comparably awesome comprehensions is Haskell which (opinion ahead) offer
by BWStearns 11y ago
Python list/dict comprehensions are awesome. The only other language I've seen that has comparably awesome comprehensions is Haskell which (opinion ahead) offers easier access to more power at the cost of some readability upfront.
Sadly I've seen functional operations like map/filter/reduce etc discouraged in (commercial) projects I've worked on, though I guess there's a bit of an argument there for debugability (make it a word?) since hunting down side effects may be more onerous?
- sago 11y agoI think the logic was that comprehensions should replace map and filter for most cases. [fn(a) for a in seq] for example or [a for a in seq if pred(a)] and so map and filter are 'old'-style and deprecated. But I have to agree, I think that's not a clear benefit. For example map(fn, seq) and filter(pred, seq) are clearer. Once you get more complex, however, the comprehension syntax does shine. Comprehensions don't help at all with reduce, of course, which has been tidied out of __builtins__ now.
- hvm 11y agoI think it's a matter of taste or what you're used to. I find the first two examples clearer than the map/filter ones. Are map and filter more concise? Yeah but only in this case. If you would need to do some processing, they would get messy fast because of a lambda.
- emidln 11y agoIn my experience, the more complicated processing ends up being composed up of smaller units (that are testable), and then map/filter remain the most clear way of representing the iteration. Using lambdas for non-trivial process is just as bad as throwing that same complicated processing into a list comp.
- sago 11y agoI work with a lot of people who have never drunk the functional koolaid, professionally. They have to do an extra mental step with map or filter - "oh map, that's where the first argument is applied to each item in the second - I think - perhaps the second argument is applied to the first, right, Stack Exchange". More explicit is clearer, then. 'Clear' like beauty, is subjective. But productivity isn't. And whatever my personal feelings, the productivity of the team wins. On the second point. I agree. Python's lambdas are a code-smell, for me. Which I don't say lightly, I'm a Scheme/Lisp guy deep down. But they fundamentally work against the aesthetics of the Python language, imho. A local function declaration is much less fragile. But I would say that as code complexity increases, in my experience, comprehensions don't last long either. They've a narrow range of applicability before you get a big block of spaghetti code. Then it is much better to define a local function and, essentially, 'map' it (even if that 'map' is done as comprehension). Of course, YMMV.
- BWStearns 11y ago100% agree that lambdas fight python's aesthetics. On the expression complexity front I can definitely say it's easy to slowly increase their complexity until they're no longer readable. As a side note, comprehensions in Python (2 at least, not sure about 3) leak scope a bit which can lead to some really funky bugs if you're in a loop and absentmindedly use a loop variable in a comprehension. >> x = 5 >> [x for x in range(10)] >> x # => 9
- sago 11y agoYes, that was a pain. Particularly because it was only true of list comprehensions, not dict comprehensions or generators: Python 2.7.10 ... >>> a = 3 >>> [a for a in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> a 9 >>> a = 3 >>> list(a for a in range(10)) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> a 3 Guido called it Python's 'dirty little secret'. It is fixed in Py3 though.
- emidln 11y agoSome lisps have similar comprehensions. Common Lisp has `iterate` (listcomp) and arguably `loop` (listcomp). Clojure has `for` (genexp) and `(doall (for ...))` (listcomp). A very common clojure library (Plumbing) implements dictcomps via for-map, although you could do python 2.6-style dictcomps yourself via (defmacro dict-comp [bindings & body] `(into {} (for ~bindings (do ~@body))))