stuff = [cleanup(s) for s in stuff if s.unprocessed]
This, the way you can handle/manipulate strings and the powerful included libraries make python a good language.Downsides? Dependency managment, tooling, speed
stuff = [cleanup(s) for s in stuff if s.unprocessed]
This, the way you can handle/manipulate strings and the powerful included libraries make python a good language.Downsides? Dependency managment, tooling, speed
With Python3 there is a move towards "idiomatic" Python code, which to me means moving away from clear and concise to the old programmer trap/readability nightmare of "look how many things I can get it to do with one line of code!"
Not sure if it would make my top-5 Python-hate list (1-4 would probably be dedicated to packaging and distribution), but list comprehensions definitely feel like a misstep that contributes little value to the language.
// JS
stuff = stuff.filter(s => s.unprocessed).map(cleanUp);
// Java
stuff = stuff.stream().filter(s -> s.unprocessed).map(Main::cleanUp).toList();
// C#
stuff = stuff.Where(s => s.Unprocessed).Select(CleanUp).ToList();
Java streams and C# LINQ extensions also expose other nice data processing functions, eg. sum, takeWhile, distinct.And if you like Python’s dictionary comprehensions:
{str(n): n * 2 for n in range(1, 11)}
you can use ToDictionary in C#: Enumerable.Range(start: 1, count: 10).ToDictionary(n => n.ToString(), n => n * 2);Sure, but the Python code is so much nicer.
I don’t even like Python all that much but it’s comprehensions and generators are very sweet.
So, given the dictionary {"a": 1, "b": 4, "c": 2}, if you want ["b=4", "c=2"], you can do this in Python:
d = {"a": 1, "b": 4, "c": 2}
l = [f"{k}={v}" for k, v in d.items() if v % 2 == 0] # replace [] with () if you want a generator instead of a list
Or this in C#: var d = new Dictionary<string, int> { {"a", 1}, {"b", 4}, {"c", 2} };
var l = d
.Where(pair => pair.Value % 2 == 0)
.Select(pair => $"{pair.Key}={pair.Value}")
.ToList(); // remove this if you want an IEnumerable<string> instead of a List<string>
The Python version has tuple unpacking, whereas the C# version iterates over KeyValuePair<TKey,TValue>. Slightly more typing, and perhaps slightly less ideal if you’re dealing with tuples without field names, but still pretty readable. The C# solution is also more obvious about the execution order.And if you want to sort the output by the dictionary value?
l = [f"{k}={v}" for k, v in sorted(d.items(), key=lambda kv: kv[1]) if v % 2 == 0]
# 5 3 2 1 2a 4 <- Execution order
var l = d // Execution order: 1
.OrderBy(pair => pair.Value) // 2
.Where(pair => pair.Value % 2 == 0) // 3
.Select(pair => $"{pair.Key}={pair.Value}") // 4
.ToList(); // 5I find the syntax of list comprehensions ugly and inscrutable.
This is basically selling point for comprehensions. You can't add complexity to them so any comprehension you see fits a few common loop patterns you can grok immediately.
If you see an actual for loop in python it means "pay attention something odd is probably going on."
This example…:
a = …
vs = [
v(x)
for b in a.f()
for x in b
if x and c1(x)
and not c2(x)
]
…becomes: a = …
def each_v():
for b in a.f():
for x in b:
if not c1(x): # comment
continue
if c2(x): # comment
continue
if x:
yield v(x)
vs = set(each_v())
Line orienting each conditional makes commenting much easier. The collection used for vs is clear. The formatting is less susceptible to being churned up by black. You get to give the thing a name, which helps.stuff = stuff.map {|s| cleanup(s) if s.unprocessed }.compact
1. What does the anonymous function return if s.unprocessed is false?
2. If it returns nothing, do I get a missing value element in the iterable that results from map?
3. And what does it mean to compact the result of the map call?
The Python snippet raises none of these questions in me. But it could be because I am familiar with Python’s quirks.
Interestingly I wasn't exactly sure what the python code would do, which is why I used the compact. In the python code, does 'stuff' end up only having the result of the cleanup function for items that are unprocessed? Or will it have all of the items, and the ones where unprocessed returns false just end up being returned unmodified?
My Ruby version returns the processed subset of items that were unprocessed. The python version was not clear to me, since I don't know what happens if s.unprocessed returns false.
So basically, it sounds like we were both confused by the same parts of the language we are unfamiliar with... what happens to elements where s.unprocessed is false? It was clear to me that in Ruby it returns a nil, but I have no idea in Python... what happens?
Python’s list comprehension combines map and filter.
[process(s) for s in stuff if s.unprocessed]
is equivalent to map process (filter isUnprocessed stuff)
in Haskell.Python comprehensions in particular are unfortunate because the flow of the query, like in SQL, is not linear - the projection part is written first, but happens last. Thus, reading a non-trivial query requires moving back and forth to resolve the references.
Compare that to C# LINQ, which is also a kind of sequence comprehension. That one not only has linear syntax where the data flow in the query is strictly left to right, but it's explicitly defined by mapping it to functional pipeline style. Thus, it ends up being a strict improvement on the latter.
(Another example of "good" sequence comprehensions in this sense is XQuery FLWOR)
Chained pipelines (`foo.filter(...).map(...)`) are fine to me, certainly more verbose then comprehensions for better & worse, but I get lost in pipelines expressed as nested functions (`map(filter(a, ...), ...)`).
If its more than a very simple expression, with one simple for, and no if or very simple if, I try to use at least (more, with parens, if necessary): one line for the result expression, one line for each for clause, and one line for each if clause.
But, while the boundary is subjective, it doesn’t take much before functional pipeline style is clearer (Python lambdas being more limited and syntacticaly cumbersome, functional pipeline style isn’t as clean in Python as it is in other languages, though.)
heads :: [[a]] -> [a]
heads xs = [y | (y:ys) <- xs]
-- heads [[1],[],[2,3]] == [1,2]
Doing the above using maps and filters looks very different: heads' = map head . filter (not . null)
Python doesn't really support Haskell-like pattern matching, so the above doesn't apply.It does as of 3.10:
https://peps.python.org/pep-0622/
Not as sophisticated as what Haskell can do, I'm sure, but I think it's a good addition to Python.
(a -> Maybe b) -> [a] -> [b]
Which yields mapMaybe: https://hackage.haskell.org/package/base-4.17.0.0/docs/Data-.... so you end up with heads = mapMaybe safeHead [
if List.isEmpty items then
p “No items”
else
ul
[
for item in items do
li [ item ]
]
]if len(items) == 0: return [p("no items")] else: ul( [ li(item) for item in items ] )
I am also not a fan of convoluted list comprehension (in python) so powerful is not necessarly a good thing.
And consider this example:
[
h1 “items”
if List.isEmpty items then
p “No items”
else
ul
[
for item in items do
li [ item ]
]
]I don’t see how this can be done in Python without using mutation, which makes code scattered across multiple statements
What's wrong with its tooling?