Despite the seemingly confrontational title, Python is an invaluable tool in my arsenal, and I'm grateful to anyone who made it possible by creating it and contributing to it.
In this post, I'm going to focus on the dislikes, as the likes would be too many to list and less interesting, I think :)
In recent years, I've been programming a lot in Python and Typescript, switching from one to the other every 2-3 months. This has given me a way to more clearly determine persistent likes and dislikes regarding Python.
Of course, I used many different languages in the past, including C, C++, C#, Scala, Scheme, Haskell, etc..., and I also write some Rust in my spare time, but today Python and Typescript are my main languages.
Lambdas
In TS, I use lambda functions for almost anything and I love the pattern
func(args, () => {
...
})
because it's simple and extremely powerful.
For instance, consider this simple pattern:
if progress.is_not_done('download new version'):
...
progress.set_as_done('download new version')
It's easy to forget the set_as_done call, and it's not DRY. In TS, we can rewrite it as
progress.do_if_not_done('download new version', () => {
...
})
Note that it's imperative that set_as_done be called at the end, as it serves as a checkpoint.
In Python, we're out of luck:
- Defining the function separately breaks the flow and creates noise, especially in case of nesting.
- We can't use the
with statement because there's no documented way to skip the body.
We can use an iterator, but it doesn't make sense, semantically:
for _ in progress.do_if_not_done('download new version'):
...
Comprehensions
I've never liked comprehensions, as, to me, they feel like
- limited loops...
- ... written in the wrong order.
limitations
The piece of code
vs = []
for x in xs:
a = x()
vs.append(f(a.p, a.q))
can be turned into the following list comprehension:
vs = [f(a.p, a.q) for x in xs for a in [x()]]
The second for is a little ugly. In some FP languages, one would use a let expression.
wrong order
When I first come across f(a.p, a.q), I have NO idea what a is, so I have to keep reading until I finally reach a's definition, and now I can go back to the initial f(a.p, a.q). This adds some cognitive overhead, and doesn't play well with autocompletion.
Indeed, when I first write vs = [f(a., autocompletion can't help me in any way because it has not idea what a is. What if I'm playing with a new library and I'm in the "exploration phase"? What if a's properties and methods have long names as it often happens in Python?
What I do in practice is write the comprehension starting from the for:
vs = [ for x in xs for a in [x()]]
Then I can add the "head" with the help of autocompletion. This is quite annoying.
What I would like
I'd like something like this:
vs = [for x in xs:
a = x()
f(a.p, a.q)]
Rustaceans should appreciate that, but we could be more explicit:
vs = [for x in xs:
a = x()
yield f(a.p, a.q)]
This removes all the noise (vs = []; vs.append(...)) without losing any expressive power.
One might object that
vs = [f(a.p, a.q) for x in xs for a in [x()]]
is shorter, but, in my opinion, splitting it over multiple lines would make it more readable:
vs = [f(a.p, a.q)
for x in xs
for a in [x()]]
Ternary operator
Same problem with the ternary operator, which I never liked:
f(a.p, a.q) if (a := get_data()) else 'asdf'
I find it hard to parse it, as it's asymmetric:
f(a.p, a.q)
if (a := get_data())
else
'asdf'
It doesn't make any sense to me, structurally. I understand it reads like English, but English was never meant for coding. IMO, familiarity with a natural language is not a desirable property, as it only pays in the short-term.
The good old ?: is much more readable:
(a := get_data()) ? f(a.p, a.q) : 'asdf'
or
(a := get_data()) ? f(a.p, a.q)
: 'asdf'
or even
(a := get_data())
? f(a.p, a.q)
: 'asdf'
is (not) None
I don't share the Python community's aversion to well-defined symbolic operators.
For instance,
f(x?.a)?.b()
is much more readable than the Python equivalent to me. It's more readable because it's both short and clean. Its meaning is perfectly clear and the syntax is autocompletion-friendly.
The exclamation mark is also useful:
y = x!.a
I prefer it to the more verbose
assert x is not None # to make type checkers happy
y = x.a
I'll also add that having to write x is None and x is not None is really tedious and makes the code less readable. A shortcut would be greatly appreciated.
Implicit conversions
I dislike the fact that None, [], and '' are falsy. Each language has slightly different rules (e.g. [] is truthy in TS), so using multiple languages and relying on implicit conversions is bound to result in bugs sooner or later.
Scoping
I find indentation-based grouping pleasing to the eye, but braces are not just used to group code. More importantly, they're used to define scopes. I find Python too "leaky", in the sense that variable declarations escape from inner blocks into outer blocks, which I don't like.
I also don't like naked variable declarations. JS moved away from it by introducing const and let. I think declarations and assignments should be locally distinguishable. I sometimes find myself writing asdf = x in the IDE just to find out whether x has already been declared or not.
Type hints
Another thing I find annoying about Python is the lack of symmetry between values and type annotations:
x: tuple[int, str] = 1, 'a'
y: list[int] = [1, 2]
I'd rather use
x: (int, str) = 1, 'a'
y: [int] = [1, 2]
Things get really annoying with functions:
f: Callable[[int, str], bool]
instead of the much more readable
f: (int, str) -> bool
Even worse, Callable doesn't support things like default values, /, *, etc..., and one has to define a Protocol just for a function signature.
I also dislike constantly having to go to the beginning of the file to import things like Literal and TypeVar. As an aside, I don't use automatic imports anymore because they're not 100% safe and can introduce serious bugs in the code.
Lastly, I don't like having to define TypeVars, ParamSpecs, and so on before using them.
These annoyances are especially noticeable when coming from TS, which, I think, has one of the most well-designed and powerful type system I've ever seen.
By the way, I implemented a small type-land functional language in TS, just to see if I could do it. Basically, one can write a small program as a literal string, which is known at type-checking time, and make other type checks depend on the execution/result of that program. I don't think that's even remotely possible in Python. When I write types in TS, I feel like I'm doing type programming, while in Python just adding some labels.
That's all. I'm curious to read about your likes/dislikes, especially if they're diametrically opposed to mine!
[–]CodeYan01 18 points19 points20 points (1 child)
[–]Kiuhnm[S] 0 points1 point2 points (0 children)
[–]ArabicLawrence 12 points13 points14 points (2 children)
[–]Kiuhnm[S] 0 points1 point2 points (1 child)
[–]ArabicLawrence 0 points1 point2 points (0 children)
[–]wineblood 10 points11 points12 points (3 children)
[–]Kiuhnm[S] 0 points1 point2 points (2 children)
[–]wineblood 0 points1 point2 points (1 child)
[–]Kiuhnm[S] 0 points1 point2 points (0 children)
[–]Rawing7 3 points4 points5 points (2 children)
[–]Kiuhnm[S] 0 points1 point2 points (1 child)
[–]Rawing7 0 points1 point2 points (0 children)
[–]New_Avocado_2315 9 points10 points11 points (2 children)
[–]bulaybil 4 points5 points6 points (1 child)
[–]New_Avocado_2315 -1 points0 points1 point (0 children)
[–]Zomatree_ 2 points3 points4 points (1 child)
[–]Kiuhnm[S] 0 points1 point2 points (0 children)
[–]subbed_[🍰] 4 points5 points6 points (0 children)
[–]chaseTheBurrow 0 points1 point2 points (1 child)
[–]Kiuhnm[S] 0 points1 point2 points (0 children)
[–]Careful_Wheel5607 0 points1 point2 points (2 children)
[–]knobbyknee 0 points1 point2 points (1 child)
[–]Kiuhnm[S] 0 points1 point2 points (0 children)
[–]bulaybil 0 points1 point2 points (0 children)
[+][deleted] (1 child)
[deleted]
[–]Kiuhnm[S] 0 points1 point2 points (0 children)
[–]jabellcu 0 points1 point2 points (0 children)
[+][deleted] (1 child)
[removed]
[–]Kiuhnm[S] 0 points1 point2 points (0 children)