I've been writing Python since 2020, mostly for the Weekly Challenge
small programming problems, and there are things that irk me about it
A disclaimer first, because I know people like to get very angry
about programming language choice. I don't love Python. I don't
think it's bad or should be condemned, and I don't think less of you
for using it; I do think it makes some significant mistakes in the way
it does things, but if you're happily writing Python, I wish you joy
of it.
And of course there are things that are just a matter of taste, like
the significant whitespace, and I'm not going to argue about them
here; I can live with them, even if my personal preference is
basically for a C-like structural syntax.
However, where this does run into trouble for me is when one's
exiting multiple loop levels at once, as it might be:
for x in xrange:
for y in yrange:
do_something(x, y)
the_next_thing()
and I at least find it hard, especially if the do_something
is
itself a multi-line block, to work out where the_next_thing
is going
to get involved. Having a closing brace (on a line on its own, in my
preferred style) makes it clearer to a quick scan how many levels of
loop I'm leaving.
There are basically two ways to structure a language, with leading
or trailing functions (or methods as appropriate). Perl for example
uses leading functions like int(x)
, Raku and Ruby put methods after
the arguments like x.chars
, and either of these works if it's done
consistently. Python does both, and you have to remember: is the
length of an array array.len()
or len(array)
? How about the values
of a dict, dict.values()
or values(dict)
? The answer seems to
be—and I have never seen this written down, I'm just guessing—that if
a function can be used on more than one sort of thing, it's a
stand-alone function that comes before its argument; if it only
applies to this specific thing, it's a suffixed method. (So
len(array)
because you can also do len(string)
, but
dict.values()
because nothing else has a values method.) And of
course the same applies to library code, because nothing seems to be
allowed to extend the methods that exist for a built-in class.
Ranges are start-inclusive and end-exclusive, without the
option. That's great if you're iterating over an array with indices
from 0
to len(array)-1
, but that does not cover every case of
iteration, and having to remember the +1 is tedious. (Rust does this
too, but at least you can do ..=
to have an inclusive range.) Also
ranges aren't quite first-class objects; trying to use them as if they
were seems to break things in odd ways.
It's slow. In my tests it's usually a bit faster than Perl or
Ruby, but not compellingly so, and that difference is lost in the
noise when you're going up against a compiled language like Rust or
Crystal, or even JavaScript with Node.
It's unstable. Features come and go within a few minor versions.
There's no way to run Python2 code under Python3 (though Perl managed
it with 4 to 5 just fine, some years earlier). I can run my early 1998
Perl5 code on a modern Perl with no problems at all, rather than being
forced to rewrite things every year or two if I used something that
someone has decided is bad.
It feels like a language that is the only language people learn, so
they never realise that any of the problems could have been done
differently and better. (Much like PHP in that respect.)
Eh. It's not going away, and it's not the Great Satan. But nor is it
the answer to everything that its boosters would like to think.