Published: Tue 18 December 2018
By Wayne Werner
In tech .
tags: python response programming error
I just read this post about the 8 Reasons Python Sucks and the way it reads to an experienced Python developer is "8 ways I don't really understand Python and really annoy me!"
Honestly, it feels like it could've been written by Zed A. Shaw.
I'm cool with not liking something after you well and truly understand it - I
disliked Emacs because it wasn't Vim for a while. Then I decided to give it a
fair shake and actually edit code with Emacs. I should write a post about why I
actually don't like Emacs now that I've used it.
Anyway, in the spirit of education, here are the reasons why the OP thinks
Python Sucks, and the reasons that they could instead!
Reason 1: Versions
It's totally correct that the change from Python2 to Python3 was a huuuge
pain in the butt for the Python community, and that started to form a rift in
the minds of a lot of people. And taking Python2 compatible code and trying to
run it on Python3 was a huge issue (mostly because what worked in Python2 was
correct for like 99% of the time that you weren't doing web programming, but
was entirely wrong everywhere else! And it tried to do the right thing but if
you didn't actually do the right thing you might not catch the bug until quite
a way down the road).
My code for Python 3.5 won't work with the Python 3.7 installation unless...
Wrooooong. The only backwards incompatible version I'm aware of has been
2.7 -> 3. I am not aware of any feature that's in Python 3.5 that has been
removed in Python 3.7. You can't go the other way, with the addition of
f-strings and async keywords, but name one other programming language that has
introduced new features that you can use in previous versions of the language!
If you are a systems programmer, I absolutely agree that Python3 was a step
backwards, because suddenly a lot of the things that make sense on a Linux
system require an extra step. However, if you are writing web software, or
anything unicode aware, then you shouldn't be mad about Python3 breaking
backwards compatibility. It makes you write more correct code because now you
actually have to understand that bytes and strings are two different things -
and as long as you remember BADTIE (Bytes Are Decoded, Text Is Encoded) then
you can pretty much largely ignore the new distinction and just .encode
when
you have text and need bytes and .decode
when you have bytes and need text.
The most accurate thing that sucks is that Python3 isn't Python2
backwards-compatible (though, really, that's actually a good thing).
Reason 2: Installation
Yes, Python packaging does suck. It's something the community has known for a
while and we haven't ever been able to hit on the best most painless solution.
Pip does a reasonably great job and is good enough for most needs. When you're a
community, though, things don't move quickly. In recent years there has been a
pretty significant amount of progress made in decoupling "build Python software
packages" from the tools that have been the de-facto standards (pip and
setuptools in particular).
We're actually at a pretty reasonable point today, though there's a lot to wrap
your head around if you want to install packages the right way. Which is
unfortunate, because you can totally get into trouble in non-obvious ways if
you're a newbie.
With most software packages, you can easily run apt, yum, rpm, or some other
install base and get the most recent code. That isn't the case with Python.
If you install using 'apt-get install python', you don't know what version
you're actually installing, and it may not be compatible with all of the code
you need.
Says someone who obviously hasn't tried to get the latest version of any
software package. Most of the software in your Ubuntu repositories is 6 months
old or older. I would bet over 50% of the software that you have on your Linux
distribution is not the latest and greatest.
The biggest difference is that with most software you don't really care. The
CPython versions are on a 6-month release cadence I think? That's actually
really fast!
But I just recently was trying to install some software that's not in my repo.
Heck, the version of the fish shell that's in the current Raspbian PPA is a
couple of years old! And it's not compatible with a script that I wrote to
generate new blog posts - and it has nothing at all to do with Python!
Python packaging does need a lot of attention - if you're interested, join the
Python Distutils
SIG and help
make it better!
Pip stands for "pip Installs Packages", because someone thinks recursive
acronyms are still funny
They are. Also Pip is about 10 years old. "GNU" means "Gnu's not Unix" because
someone thinks recursive acronyms are still funny.
Earlier this year, a version of PyPI was found to have a backdoor that stole
SSH credentials.
No, it wasn't. A software package on PyPI was. This is like calling you a
hacker because you downloaded a virus.
Saying that Python Sucks because it has a community repository of software to
supplement the standard library is hilariously bad reasoning.
By the way, who maintains these pip modules? The community. That is, no
clear owner and no enforced chain of provenance or accountability.
Hilarious and entirely wrong! Pip modules? That doesn't mean anything! You
could say, pip installable modules, but pip can install from wherever you tell
it to, so that doesn't really mean anything. The community absolutely does not
own packages on the Cheeseshop PyPI - each package maintainer
does.
I don't use Node.js and npm for the same reason; I don't trust their community
repositories.
Just as with NPM there are very specific owners of packages and package
versions. Can someone subvert them? Sure. Have they been? Absolutely! Will more
be in the future? Without a doubt. But that's not a language problem - that's
an ecosystem problem. That exists because companies are more than willing to
build off the sweat of other people who built things that they love, but much
less willing to pay for that sweat.
In any case, if you don't trust Python's community repository, great! Don't use
Python's community repo - you can actually get stuff done with the standard
lib! You can't tell me the same thing for node. And probably not for many other
languages. (And also, I hope you're programming using a compiler or a language
that's developed by a company that you're paying I guess instead of one that's
created by a community?)
And hey, if you're willing to spend money to pay people to write software in
Python and be accountable for the libraries, you will find people lined up at
your door.
But yeah, installing Python packages properly is... difficult for the beginner,
and that's a problem.
Reason 3: Syntax
I just saw the title of this and I laughed.
And at first glance, Python seems very readable. That is, until you start
making large code bases.
Spoiler alert - the complaint here is actually not that Python doesn't have
statically typed variables! It's that Python uses indentation to denote scope!
I'll wait until you're done laughing.
Deep nesting is permitted, but lines can get so wide that they wrap lines in
the text editor. Long functions and long conditional actions may make it hard
to match the start to the end.
Okay, if your codebase has lines so wide that they wrap in your text editor I
understand why you find large code bases hard to maintain, but Python isn't the
problem here...
If your functions are long enough that they can't fit in one terminal, and if
they're wide enough that they can't fit in one terminal, your code is probably
doing too many things! If you think code that looks like this:
def some_func():
x = 1
y = 2
return x + y
Is somehow inferior to:
int some_func(){
int x = 1;
int y = 2;
return x + y;
}
Or worse:
int some_func(){
int x = 1;
int y = 2;
return x + y;
}
You have some serious problems.
It doesn't get better by adding more code and indentations to it.
And I pity anyone who miscounts spaces and
accidentally puts in three spaces instead of four somewhere -- this can take
hours to debug and track down.
Lol. Okay. I guess if you can't read this
File "/tmp/nope.py", line 6
print("You can't find me! Ha ha!")
^
IndentationError: unindent does not match any outer indentation level
Then it would take hours to debug and track down.
Compare that to:
if (some_condition)
do_something();
do_something_else(); // Lol, always happens
And I can totally see how the Python syntax sucks... if you're from the NSA.
The OP does make a valid point that if you have the habit of putting debug code
in without any indents it's harder to find, but maybe pick up a new habit? I
use one of the two:
Add logging code with logger.debug('some message')
Preface the section with #REMOVEME
and end with #REMOVEME end
Then I just use my TODO comment list to find sections of code to remove.
Python's indention as a reason it sucks is... hilarious. If you'd like to
complain about anything with the syntax, maybe complain that annotations are
ugly?
Reason 4: Includes (ed. should be imports )
Python's import permits including an entire module, part of a module, or a
specific function from a module.
No... actually it doesn't. The Python import
statement evaluates the module
at the very place it's imported. As a matter of fact, there's no such thing as
include in Python! When you run python <filename>
the entire file is compiled
and executed immediately! The only thing that isn't executed is code found
inside function definitions, but the way classes and functions exist is that
the code is executed and the result is a function or class definition!
When you import a function from another module, e.g.
from functools import wraps
The entire functools
module is evaluated - the only difference is that it
only stores a reference to functools.wraps
, rather than the functools
module.
Honestly, feel free to complain that Python actually evaluates code at import
time.
Unless there's a namespace conflict
Lol wat? There's no such thing - unless you mean the thing that probably every
Python developer does when starting out and creates a file called random.py
and then learns that importing will import the local random.py
and not the
real random module and then they delete the random.py
file but not the
random.pyc
file and spend 6 hours trying to figure out why their code doesn't
work anymore. But that's not a conflict - that's shadowing the name of a
builtin, and yeah, if you shadow an existing module you're in for a bad
time... but is that something that you could avoid in other languages? I'm not
sure. I do know that if you have your own module you can totally have a
submodule random
, because from . import random
is entirely different than
import random
and you could do import foomodule.random
then, with no
issues.
Reason 5: Nomenclature
In every other language, arrays are called 'arrays'. In Python, they are
called 'lists'.
Lol, no. In Python, arrays are called arrays. They're right there:
The problem is that in Python, for the most part, an array is useless! Python
lists are actually lists! They can hold anything, and they're "unlimited" in
size! At the very least, you can append to them until your OOM killer on your
OS decides your process has had enough fun. Lists are not arrays (although we
happily conflate the two most of the time in Python, sorry about that.)
an associative array is sometimes called a 'hash' (Perl), but Python calls it
a 'dictionary'.
TBH, I don't know if there's a proper difference between a hashmap/hashtable
and a dictionary in Python, but even if there isn't Python was originally
designed as a teaching language (and still aims for that), and dictionary is
vastly superior to hash for beginners.
PyPy, PyPI, NumPy, SciPy ... I understand the 'py' is for Python. But
couldn't they be consistent about whether it comes first or second?
Well, those are all community efforts, which you don't trust, so... why are you
worried about their names?
Most Python libraries seriously suck at documentation
Well, yeah, that's true of most software in general . On the other hand, give
me an undocumented Python module over an undocumented ANYTHING ELSE any day. I
have yet to see really and truly terrible Python code that wasn't in use in
code golf.
matplotlib, nose, Pillow, and SQLAlchemy. And while some of the names may
give you a hit to the purpose ... others are just random words.
So you wouldn't guess that matplotlib is a library for plotting math things?
Nose is a tool for code smells (which is dreadfully punny, even though it
doesn't include 'py' in the name). Pillow is kind of an outlier there because
it's actually a fork of the seriously(?) named PIL (Python Imaging Library)
project. As a newcomer to Python that might be something confusing, but anyone
who's been using Python for more than 5 or 10 years is probably aware of PIL
(and you still import from PIL, not pillow... because someone else owns the PIL
package on PyPI, rather than the community).
Reason 6: Quirks
Python has more quirks than any other language I've ever seen.
Okay, so the behavior of 1+'hello'
in JavaScript or Perl is less intense than
quirks you find in Python? Really? Really?
In Python, there's no difference between single quotes and double quotes.
However, if you want your string to span lines, then you need to use triple
quotes. And if you want to use binary, then you need to preference the string
with b (b'binary'
) or r (r'raw'). And sometimes you need to cast your
strings using
str(string), or convert it to utf8 using
string.encode('utf-8')`.
Um... wow. I don't think anything here was accurate in the least! What you've
just said is that "Python has multiple shortcuts for dealing with strings and I
haven't bothered to learn what they mean".
In Python, as in C, PHP, Bash, and JavaScript, you can make a string span
multiple lines by using concatenation and newline escapes:
this_is_ugly = (
"As newlines go\n" +
"I don't really mind them\n" +
"But they're kind of terrible\n" +
"When you have other string formatting options\n" +
"at hand."
)
When you could just write this like:
this_is_not_so_ugly = """As newlines go
I don't really mind them
But they're kind of terrible
When you have other string formatting options
at hand.
"""
Or even better:
from textwrap import dedent
this_is_beautiful = dedent ( '''
As newlines go
I don't really mind them
But they're kind of terrible
When you have other string formatting options
at hand.
''' ) . lstrip ()
I guess you could call that quirky that you don't have to concatenate your
multiline strings.
The worst part though:
If you want to use binary, then you need to preference the string with b
No. That is not what that means at all.
If you want to use binary you have to do the trivial conversion to hex:
Or:
>>> print('\x1234\x9999\x4242\x100000')
3499B420000
On the other hand, if you want a byte string (remember that whole rift in
Python2 and Python3? This is pretty much all of that) - which can hold things
that are not, strictly speaking, text (see that previous example, here you go:
>>> print(b'\x1234\x9999\x4242\x100000')
b'\x1234\x9999B42\x100000'
Now this is a byte string - you're telling Python that this is not text, it's
binary data. Not "using binary".
And raw strings are entirely different! Raw strings tells Python not to use
escape sequences ("\n"
should not be a newline character). You can do that by
escaping the escapes, like "\\n"
... but if you're writing a Regex, that can
get really ugly really quick. Instead you can just put r in front and have
r'some \d\W\d+ regex that does (not)? need extra \ in it'
.
I wouldn't call that quirky any more than I'd call the fact that you can write
regex in Javascript without quotation marks quirky, or the fact that in C(++)
there are string types and character types instead of characters being a string
of length zero.
There are other quirks in Python in regards to scoping or other weird things
that you absolutely shouldn't use , but
Python's strings are not these things.
Reason 7: Pass by Object Reference
I'm not sure if calling it pass by object reference is correct, but Python
does, in fact call by object
reference . This is arguably more
quirky than strings, because most C-based languages pass by value, i.e. they
copy whatever they're passing to functions. This is confusing to people used to
C-style languages because there's no such thing as an "object". In C you have
"structs" but these are just a collection of data. If Python sucks because it
has a different object model from C then so does every other language in
existence.
The biggest reason this is confusing for newcomers is because in Python
everything is an object, and you're wrong when you think of variables that
way. They're just names for objects, and some objects are immutable (strings,
tuples, numerics), while others can be changed (lists, dictionaries, sets).
That's it, that's the whole thing. If you don't like mutable types, then throw
out C++ (well, the STL anyway), Java (don't mind if I do!), .NET... well,
again, basically every language but C, because they all
suck!
Reason 8: Local Names
It's a common programming technique to name the program after the library or
function being used... With C, Java, JavaScript, Perl, PHP, etc., this works
because the language can easily distinguish resource libraries from the local
program; they have different paths. But with Python? Don't do this. Never do
this. Why? Python assumes you want to import the local code first. If I have
a program called screencapture.py
that uses import screencapture
, then it
will import itself rather than the system library.
That's... accurate but also wrong? This is just Python's import mechanism at
work, and also is the exact same thing that makes C or Java work. That's why in
C header files you have #ifndef FOO #define FOO...
looking code. Python looks
on the PATH for files to import, and by default the first place to look is in
.
. But you could easily change that. Just do something like the following:
mkdir /tmp/frabble
echo "print('razzle frabble')" > /tmp/frabble/fun.py
cd /tmp/
$EDITOR fun.py # I hope you have $EDITOR defined!
then in /tmp/fun.py try this:
import fun
print ( 'So much fun!' )
Then run it both python2 fun.py
and python3 fun.py
should work (either one
will look the same, despite not having to refactor the code...) and you'll see
So much fun!
appear twice on your command line. Now just add two lines to the
top of your code:
import sys
sys . insert ( 0 , '/tmp/frabble' )
import fun
print ( 'So much fun!' )
When you run it now you'll see this:
razzle frabble
So much fun!
Because now Python is looking for your code in a particular place when it
imports. I'm not sure if Python puts .
on your PATH or if that's something
that your environment does, but if you want to avoid problems with your import,
just make sure you don't have collisions on your PATH. I'm pretty sure that not
even C will fix that - if you've got the wrong header file on your path you're
going to have a bad time compiling, and if you have the wrong library version
on your path you're going to have a bad time. I mean, I guess that's a reason
that Python sucks, but that doesn't seem like a problem that's unique to
Python.
Not All Bad
Well, the original article wasn't all bad, but the author got a lot of things
wrong about Python. I can understand getting frustrated about a thing and not
wanting to pick it up and actually understand more because you're so frustrated
with it, but I hope the author 1) reads this reply and 2) understands Python
enough to gripe about the real problems with Python.
I'm totally fine with people disliking Python for the legitimate problems that
Python has, or even because they really just like to have static binding on
their variables (or even, like Rust, at least static binding until it's
explicitly rebound to a new type) and they really hate that Python does that!
But I do get bothered when people dislike a thing and they're mostly or
entirely wrong about the thing they dislike! That's just silly!