{ karel.codes }

Python: The good, the bad and the ugly

programming

I like Python, the programming language. I have used it professionally and in my own time. Like all languages, it is not perfect. Let’s see what makes Python good and not so good.

The good

  • Being one of the most popular programming languages, it is easy to search for solutions to any problem that you might have. There are loads of libraries available. There is also no shortage of Python jobs.
  • The syntax is easy to read. I personally do not mind significant whitespaces because it helps make peoples' code formatting more uniform. (but see below…)
  • Fast to write, so it is suitable for building prototypes and small to medium size applications.
  • The “batteries included” standard library. It’s not neccesarily the best stdlib in modern times, but it has inspired other language designers, such as the Go designers, to also provide a useful standard library.
  • Excellent choice for data science thanks to the SciPy, NumPy, Pandas, Scikit-Learn, and related libraries. No other language is as versatile in this area (and I don’t like R).
  • It was invented in Amsterdam. Mooi!

The bad

  • Lack of parallelism. Yes, the threading and multiprocessing modules exist, but they are too difficult to use and can only handle embarrassingly parallel problems. Asyncio offers concurrency but not parallelism - it is single threaded. To be fair, to my knowledge no interpreted language offers proper parallelism. However, the kinds of problems that some people want to solve with Python necessitate parallelism. The Python solution is to off-load parallelism to C: NumPy, PyTorch, etc. But you are out of luck if you need parallelism for a task that does not involve crunching numbers.
  • The object model is too mutable. Just about any aspect of an object, a class, or a module can be modified at run-time, including that of most builtins. This means that neither you nor the compiler can make assumptions about how your code will execute. That makes it impossible to apply compile time optimizations, and it is one reason why Python is so slow.
  • Dynamic typing not suitable for large code bases. Once a critical mass of Python code has been reached you will spend a lot of time second guessing your assumptions about the data types of function parameters and return values. Python now has type annotations to help deal with this problem, but they are entirely optional and do not affect a running program at all: You may have annotated that the return value of a function should be an integer but it can still return a string. What is the value of dynamic typing if we need to type annotate our code anyway? I prefer smaller code bases without annotations to larger code bases with annotations.

The ugly

  • Dependency management. Poetry is the best solution I have found so far. I like it. But one major problem is out of its reach:
    • Library developers are prone making breaking changes without properly using semantic versioning. This leads to the problem that upgrading any of your dependencies may break either your code, or the code of another library that also depends on it. And you probably won’t know until after you have run your application.
  • The statement “There should be one– and preferably only one –obvious way to do it” doesn’t apply to Python anymore.
    • The stdlib offers at least four different concurrency models: threading, multiprocessing, concurrent, and asyncio. Yet none of them can deliver efficient parallelism due to the Global Interpreter Lock.
    • There are also at least four code formatting tools: autopep8, black, prettier, and yapf. Out of those I prefer black, but why do we need more than one way to format our code?
      • PEP8 specifies a maximum line length of 79 characters, which is just too short. I configure black to 120 characters. We all have widescreen monitors these days.
    • Django, Flask, FastAPI, Pylons, Pyramid, Tornado, Zope, Sanic, Dash, AIOHTTP, … This is only a small sample of web frameworks and libraries that Python has to offer. There are simply way too many options here, and they all perform essentially the same task: Serving web pages and APIs. Out of the ones I’ve listed I have used Django, Flask, FastAPI and Zope. I prefer FastAPI because it’s similar to Flask but with asyncio and input validation. Django is all right. Zope is by far the worst, incredibly slow and it has a maddening object model (to be fair, the first and last time I used it was over 10 years ago, how time flies!).
  • The language complexity keeps increasing. One of the selling points of Python is that it is easy to learn for newcomers. Yet the Python designers keep adding more language features. Do we really need pattern matching? The type annotation system also keeps becoming more complex. One day it will become as complicated as TypeScript.

Conclusion

After reading the above you may get the impression that I dislike Python, given that I listed more negative than positive points. Nothing could be further from the truth. I think that Python is one of the better programming languages that are available to us. Over the years I have become more aware of its shortcomings though, and I have started to move away from it for backend related tasks. I still use Python to write short scripts, use Jupyter Notebook, and use it for data science.