Re: 8 Reasons Python Sucks

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:

import array

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 usingstr(string), or convert it to utf8 usingstring.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:

>>> print('\x61')
a

Or:

>>> print('\x1234\x9999\x4242\x100000')
34™99B420000

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!

links

social