Python is not good enough

Python by William Warby. CC BY 2.0.

For the last six months, I’ve been working on some networked Python components for a robotics application. There’s a web app, some data recording, etc. I’m increasingly feeling that Python is not good enough language and ecosystem for my needs. Here’s my beef:

Debugging and profiling live systems is hard. I’d like to profile a Python process with tens of threads. Better yet, it should be done in production, because the performance issues never show up in the testing environment. Or maybe I just want to get an idea of what a weirdly-behaving process is up to. With Clojure, I’d open VisualVM and poke around. With Python, well, there’s a bunch of profilers, but none of them seem to produce much useful information.

Deploying is awkward. I have a bunch of Python projects and I’d like to deploy them on a server. In Clojure world, I could build an uberjar, send that to a server and run it. In Python world, self-contained builds do not exist. I could build a wheel, or clone a git repo, but it won’t include the dependencies. Of course, with Docker you can make a self-contained build out of anything.

Standard library. Python is “batteries included”, but the batteries leak acid. I concur. Even Guido van Rossum admits it. There are sharp corners everywhere and the development speed is glacial. And do you know if the standard library modules you’re using are thread-safe?

Poor support for functional programming. Python’s lack of persistent data structures and proper syntactic support for anonymous functions mean that functional programming is cumbersome. I’ve fixed so many list and dict mutation bugs that simply wouldn’t have happened in Clojure, because the standard data types are immutable. Come on Python, even Java has full-power lambda expressions!

Poor concurrency support. Python’s concurrency tools are quite traditional (native threads, forking, locking) and the performance has not been great. Combined with the lack of persistent data structures, writing robust concurrent programs is hard when compared to e.g. Clojure or Haskell. “You shouldn’t write concurrent programs in Python” is not an acceptable answer. I hear the things might be better with Python 3 and asyncio, though.

Limited language extension possibilities. Higher-order functions are awkward and there are no macros, so it’s hard to have your own control structures. I realize that it’s part of the Zen of Python to not be able to extend the language, but the Zen of me begs to differ. You seldom need this power, but it’s great to have when you do.

Awkward tooling. This is a personal preference, but iPython, pip and virtualenv never seem to work quite as smoothly as Leiningen and Clojure REPLs, or npm and the browser REPLs.

I don’t think that Python is a bad language or a bad ecosystem, per se. It’s just that other languages like Clojure, JavaScript, Go, and Haskell have made great strides forward in the last few years while Python has been falling behind. For some niches like scientific computing and machine learning, Python still is great: there are good libraries and an active community. For my niche of writing network servers, it’s not where the action is anymore.

To end on a positive note, here are some things I like about Python:

Docstrings. More languages should have docstrings, or otherwise a standard convention for writing function and module documentation.

Keyword arguments with default values. You see awkward implementations of keyword arguments everywhere in JavaScript and Clojure code, because they’re great!

There are some great libraries. Lately I’ve enjoyed using Requests and Werkzeug. numpy has served me well whenever I have needed it. I’m impressed by Hypothesis, although I haven’t been able to use it much.

It’s good for small command-line utilities. Fast startup, no compilation, and argparse means it’s easy to whip up a quick CLI tool.

Comments or questions? Send me an e-mail.