Skip to content

Instantly share code, notes, and snippets.

@ryanwilsond
Last active July 24, 2022 19:37
Show Gist options
  • Save ryanwilsond/ed6536f9f3515794147bc5b3b15f5aab to your computer and use it in GitHub Desktop.
Save ryanwilsond/ed6536f9f3515794147bc5b3b15f5aab to your computer and use it in GitHub Desktop.

Python Considered Harmful

In recent years the simple programming language Python has become very popular and established. It was created by Guido van Rossum in 1991 and started to pick up with the release of Python 2 in the year 2000. Python is a dynamically typed scripting language with a huge community. It even has its own IDEs like PyCharm and Spyder. Other popular IDEs including Visual Studio, Visual Studio Code, Sublime Text, and Atom also have support for this growing language. It has been well accepted and is being used more and more at large companies. It is very easy to learn and use, with virtually no setup. You can just start coding after installing it. Because of its simplicity, it is favored among non-programmers like accountants and scientists. With all of these things in mind, you may be thinking: This language sounds awesome! However it is anything but awesome because of its loose design, especially around type handling, making it easy to use, but almost impossible to use robustly. This is crippling to Python's use case because reliability and robust design are the building blocks of reliable and robust software.

Before insulting Python's design and focusing on its mistakes, let us focus on what can we learn from its successes. Looking in depth at any language can give insight into what works, and what does not. Even if the language is poorly designed or maintained, there is usually at least one takeaway.

Why Python is Good

Python's aforementioned simplicity and approachability are one of its biggest successes. This was the main contributor to Python's rise as a popular language. Because of this, there is a surplus of Python packages. Packages are constantly being updated and added, due to the large group of Python fans who strive to make the community better by making Python even more accessible than before. Not only is the community to credit, but Python's package managing system allows anybody to easily program a package and upload it for everyone to use, for free. This unified and singular method for creating Python packages is a strength that most languages lack. However, the size of the community is not evidence that Python is a well-designed language.

Dictionary Initialization

The first language feature that sicks out as unique and innovative is dictionary/map initialization. Take the example:

monthCodes = {"jan": 1, "feb": 2, "mar": 3}

This is a very simple example, but a simple example is all that is necessary because Python's dictionary initialization syntax is simple, fast to type, unambiguous, and understandable. Those are some traits that all syntax should strive for. If it is not immediately obvious, this example shows a dictionary mapping strings to integers. Every month (or just the three shown in the example) have an assigned value. This unique syntax allows developers to hardcode dictionaries without manually adding each key-value pair separately, in a very intuitive way.

Now the same functionality as implemented in C#, one of the best-designed languages currently:

Dictionary<string, int> monthCodes = new Dictionary<string, int>() { {"jan", 1}, {"feb", 2}, {"mar", 3} };

Ignoring C#'s verbosity in the way it handles object creation, the way you hardcode key-value pairs it much harder to type, harder to spot mismatched curly-brace mistakes, and harder to read. C#'s design in this manner is one of the best, but Python's method is just a little bit better in every major category, making it superior.

Unpacking

Python's approach to returning multiple values from a function is bad, as fundamentally a function always returns a single value. This can be worked around by using tuples, but Python makes that part a little unclear by automatically converting multiple values into a tuple. Fundamentally it works, but it makes it a little harder for the developer to see that. However, the receiving end of multiple values in Python is much better. Unpacking allows simple assignment to multiple variables from a tuple, not only in the context of function return values.

Here is an example of a function returning multiple values:

def MyFunction():
    return 1, 2

a, b = MyFunction()

On a single line, we just initialized two variables based on a tuple (you are 'unpacking' the tuple into its separate values). This eliminates the need for intermediate variables to store the tuple result, and then variable by variable assigned from the tuple.

Why Python is Really, Really Bad

It would be unfair to ignore all the things that make Python good, hence the previous section. However, that is not the main focus of this article. The main focus of this article is not to deter everyone from touching Python at all, because Python does have a good use case. That being simple or short scripts used as utility, or by helping non-programmers get results. Using Python for these types of tasks, and enjoying doing it is fine. Python as it stands is one of the best if not the best way to do these things. The big "however" is that Python is never the right choice for making real-world software that needs to be robust. The remaining sections in this article are made to tell you why that is.

Another note is that even if Python is the best choice for a given task, people are still allowed to have opinions and preferences about what developing environment is better for them. Because of this, it is unfair to judge people purely on what programming languages they like and use. If someone is very attached to Python (as many are) it would be better to understand what they like about it, or maybe send them an article like this and let them decide for themselves. Arguments like "My language is superior" or "You do not use this language so you are a worse programmer" are unjustified and counterproductive. Explaining to people the weaknesses of programming languages is fine, but you do not want to do it in a way that makes people like programming less or even makes them stop doing it all together. With that out of the way, Python has many weaknesses that can make it frustrating or less productive than other environments even if the developer does not know it. That is why it is my very strong recommendation to avoid using Python for serious tasks.

Dynamic Typing

A controversial topic, even though it should not be. Dynamic typing is worse than static typing. Of course, as with any statement similar to that one, you can find exceptions. But those exceptions are because static typing is not an option, not because dynamic typing is better. The only case where you can argue for dynamic typing is for simple or short scripts, similar to Python's main use case. However, you can also argue static typing makes your thoughts more clear and more explicit, allowing you to debug your script easier and understand the script better. Another argument is that dynamic typing is a bad practice/habit when it is not an option. In addition, dynamic typing spawns a lot of other problems.

While many people pride Python on how it is faster to type because you do not need types is wrong. In actuality, if you want to make robust code, a lot of extra typing is required. Because function parameters have no enforced types, you need to check each parameter to prevent runtime errors.

Here is a simple example:

def MyFunction(a, b):
    if !isinstance(a, float):
        raise TypeError(f"parameter 'a' is of incorrect type '{type(a)}'; should be 'float'")
    if !isinstance(b, float):
        raise TypeError(f"parameter 'b' is of incorrect type '{type(b)}'; should be 'float'")

    ...

MyFunction(0, 3.5)

Already a lot of typing is involved here. However, there is an issue with this code. Parameter 'a' is an integer because whole numbers default to integers. Implicit casting is not applicable here, because we are checking the types ourselves. So not only do we have to do this checking, but we also need to distract ourselves from the actual types of the parameters (floats) and check for integers as well.

Dynamic typing also makes docstrings more verbose, as now you need to specify the types of the parameters there as well.

Dynamic typing also in its nature means there will be fewer compile-time errors, but rather runtime errors. This is much worse because you want to know these sorts of problems while you are testing your software, not while it is in use.

This is nitpicking, but dynamic typing also means that Python needs a division operator for integers, and a separate operator for floats so Python knows whether to round or not.

Similar to dynamic typing, Python is also implicitly typed. This prevents implicit casts from existing. While being more explicit does not hurt most of the time, it is verbose.

Overloading

Because you can not give types to parameters in functions, Python does not allow function overloading. This is a big annoyance as programmers used to other languages use function overloading very often. It simplifies design as you can treat a bunch of functions that do almost identical things as one to make your code design easier to use and understand that even though two functions can take in different inputs, you immediately recognize that they will do mostly the same thing. There is a very clever workaround from classes in Python that allows method overloading, however, it should be much more mainstream. This workaround also does not work for functions outside of classes and is not worth the extra complexity.

Classes

On the topic of methods, classes in Python are as limited as they get. They have the functionality of a c-struct with methods. Classes do not even support privacy. There is a naming convention for this, using an underscore as the start of a method name to indicate it is private, however, it does not hide the method from users of the class. Similarly, there is no support for abstract, sealed, partial, virtual, internal, protected, private protected, templates/ generic arguments, getters and setters, read-only fields, multiple constructors, overrides, etc.

All of these things combined make Python classes harder to use because they are just too simple. That is a common theme across Python, you can not do something because Python is too simple. Python is too abstract. The Fundamental Theorem of Software Engineering is simply: "We can solve any problem by introducing an extra level of indirection", however, the second part of the phrase is important here: "...except for the problem of too many levels of indirection". Python suffers from this problem. It is so easy and simple that for any complex or serious software it is difficult to use efficiently.

Import System

Python has the award for "Worst Import System". The way you import other files in your project is tied to the relative path of where you are running the main file. This means that if your code works perfectly, just changing where you run it from can break it. An example of a good import system would be C#, where you are not actually including any files but rather they are tied to your main file, to begin with. 'Using' statements are just syntax sugar to reduce verbosity.

Interpreter

The last major point is that Python is interpreted. This has a rare use case, but most of the time just means that there are more runtime errors, it runs slower, there are no warnings, importing means running (which can be unintuitive coming from any c-style language), and there is an unavoidable dependency that whoever uses your script has to have Python installed already. Instead of bundling everything into a portable executable, there is this huge dependency.

The few topics discussed in depth here are nowhere near all the problems with Python. To show that, here is a list of some of the other problems:

  • No declarations without initialization
  • No namespaces
  • The 'Print' function uses 'end' parameter instead of having 'PrintLine' and 'Print' function
  • Out of date c-style naming conventions are used
  • Built-in functions are static that need to handle each type instead of being attached to the types themselves
  • Python on the command line has no command line arguments which limits potential like version control
  • No prefix or postfix operators
  • Whether a variable is passed-by-value or passed-by-reference is predefined and unchangeable (and not even consistent)
  • No enums
  • Lists are misleading because they are not linked lists
  • Whitespace based instead of using curly brackets or something else
  • Popular packages have terrible names that do not explain what the package does (e.g. BeatifulSoup, Pandas, etc.), but this is not Python's fault directly
  • Uses keyword None instead of null, even though null is a mathematical concept
  • None is used as a value instead of the lack of a value or unknown
  • Functions with no return value return None instead
  • No null (or None) handling operators
  • # for comments, meaning no multiline comments (and using strings is the worst possible replacement)
  • No switch (edit: there is now, but it is not even called switch)
  • Docstrings are markdown, which is cool, but not as useful as XML comments or Doxygen
  • Import aliasing is common, however, it makes code harder to understand and is just a bad idea
  • No preprocessor statements (cant preprocess when you are interpreting)
  • No constants (that are enforced)
  • Requiring self as the first parameter for every method
  • Class methods are useless and confusing
  • "Dunder" methods instead of doing operator overloading like every other language
  • No interfaces
  • 'For' statements are 'foreach' statements, which is fine but that means no traditional 'for' statement
  • No boxing or as-casting
  • No type enforcing collection type (including dictionaries)
  • Multiple-inheritance is allowed
  • Because Python is so slow all the efficient libraries including Numpy (which is very popular) are written in C instead (which says something about Python)

To recap, being disrespectful to people who like or use a certain language is wrong, but a firm prod for them to switch to something else for robust software is fine. In the end, the most important thing is following the team's standards. Now many other languages also have some of the problems mentioned in this article. That does not justify them though. But even with these faults, denying that Python has a use case is ignorant, but so is thinking that it is the best language.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment