Language Review: Yikes! A Python!
Tags: python, review
Published: 2020-01-15 04:06:30

It's no doubt that Python is popular, but to me, it's too similar to the snake it shares a name with; cool to look at, but too unreliable to handle.

Of course, there are exceptions to every rule, and some people are great at writing in Python. Take the team that created Ansible, for example. [Its code](https://github.com/ansible/ansible) is well-organized and readable. Clearly, they have a set of defined standards that they follow. The way they use Python as a language is fantastic too. They take advantage of the fact that Python is deployed on most Linux systems to make Ansible agentless. If you're unsure of what that means, you should check out my [Theoretically: Ansible](https://dev.to/rburmorrison/theoretically-ansible-2hde) post, but just know it's pretty cool!

Alright, enough complementing Ansible. Let's get to the points.

# **Good:** Easy to Get Started With

A lot of people are learning Python as their first language, and it's understandable. For one, it doesn't have all the symbols that other languages have. Look at the following two snippets from C++ and Python, respectively.

``` cpp
// age.cpp
int age = 18;

if (age >= 18) {
    std::cout << "Welcome!" << std::endl;
} else {
    std::cout << "Sorry, you must be at least 18 to enter." << std::endl;
}
```

``` python
# age.py
age = 18

if age >= 18:
    print("Welcome!")
else:
    print("Sorry, you must be at least 18 to enter.")
```

It's obvious, but the C++ snippet is more verbose in several ways. For new programmers, it's just too much too fast.

Another thing that makes Python easier to work with is the abstraction of pointers. In my experience, pointers are the hardest learning point for new programmers. In Python, everything is pass-by-reference, meaning that you can get around pointers. No need to worry about making references and dereferencing.

# **Good:** Healthy Standard Library

Python's [standard library](https://docs.python.org/3/library/) clearly ate its veggies as a kid, and that's great. It means that out of the box, you can do a lot with it. Anything from cryptography, to drawing in the terminal, to GUI applications, to database interfacing is at your fingertips, and that's something I love to see in a language.

If you use Python a lot, seriously look into what's available. You may not need as many third-party packages as you think you do.

# **Good:** Strong Community

Because Python's so popular, its community is huge. You can find a library to do just about anything. Here are the statistics for [PyPi](https://pypi.org/) as of writing this:

![PyPi Stats](https://thepracticaldev.s3.amazonaws.com/i/of81x2jz727aqxdmx8ez.PNG)

That's pretty good! They're not as high as something like Maven or NPM, but that can be a good thing. It means there's a lot less clutter to filter through when dealing with dependencies, but there are also enough options to look at.

# **Meh:** Slowness

"But machine learning is done with Python all the time! It *must* be fast!", I hear you say. That's actually false. Python wrappers are created that call compiled C libraries in the background, and the majority of the work is done in C. Python is really, really slow. I recently found a machine learning library written in pure Python that takes 3-4 days to finish! If that were written in something like C, it would take less than an hour. If you want metrics, check out either of the below resources. Whether you trust them or not is up to you, but the code is available to inspect for yourself.

- https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/python.html
- https://github.com/kostya/benchmarks

The reason this is only listed as "Meh" is because, in most modern applications, speed doesn't really matter. Most software tends to be "good enough", and if you need to do something fast, you can always write the C code to interface with it. But you're certainly not going to write a fast system monitor in pure Python.

# **Meh:** Everything's Public

There's no access control in Python. Have a utility function that's only going to be used in your package? It doesn't matter, anybody can access it anyway. If you don't think this is a problem, you're probably not considering the documentation that will be generated for your code. For other developers that want to work with what you make, it won't be immediately clear what you intend for them to use as opposed to what you wrote for internal use only. You can get around this by using an underscore (`_`) as a prefix to a private function like this:

``` python
def _my_helper():
    # do something
    pass

def my_worker():
    # do something
    _my_helper()
```

This is only a "Meh" because if you can make sure that everybody follows this simple rule, you can get around a lot of the issues that everything being public causes.

# **Bad:** Dynamic Typing

Dynamic typing is another selling point for new developers. Why wouldn't it be? There's no need to worry about the extra overhead of variable types. When you assign a number to a variable, the variable contains a number. It's as simple as that. Here's another C++ vs. Python example:

``` cpp
// add.cpp
int addOne(int num) {
    return num + 1;
}

int main() {
    int n = 0;
    n = addone(n);
    std::cout << n << std::endl; // => 1

    return 0;
}
```

``` python
# add.py
def addOne(num):
    return num + 1

def main():
    n = 0
    n = addOne(n)
    print(n) # => 1
```

If you're a fan of that, I get it. And when working on your own project that's just for you, it's fine. But when you want to work with other people, it's **very frustrating**. What's to stop me from doing this?

``` python
# add.py
def addOne(num):
    return num + 1

def main():
    n = 0
    n = addOne("hello") # adding a string
    print(n) # => ERROR
```

*Absolutely nothing*. There's no getting around the fact that, because of dynamic typing, anybody can pass anything to that function. If I were to try that with C++, I'd know that I made a mistake before the program even ran. There's are **tons** of subtle errors that occur from accidentally using the wrong types in the wrong places; problems that would be solved with static typing.

So how do you get around it? Documentation. You're expected to write good enough documentation to let users know what they should expect to work with your code. Let's re-write the function to include some.

``` python
# add.py
def addOne(num):
    """
    Add 1 to 'num'.

    @type int
    @param The number to add 1 to

    @return The result of adding 1 to 'num'
    """
    return num + 1
```

Okay, so we've documented it, and it's clear what should be expected now, right? Well, sure. At the same time, though, you had to do a lot more work than just adding the keyword `int` like you could have done in C++, which takes care of what you needed the documentation for in the first place. The other problem is that **you just can't trust most developers**. Most developers just won't bother writing documentation like that, and Python won't force them to. Static typing will, though. At the very least, even if a developer is bad at documenting, you'll know what the parameters need to be, and what you'll get back from it.

# **Bad:** Poor Default Documentation Tools

Okay, okay. So you're willing to write the documentation for your code, and you want to do it right. Good! But also, good luck. Python's documentation tools are just about the **worst** I've seen in any language I've worked with. Python's default documentation tool is PyDoc, and here's an example of what its output looks like, generated from the official `sys` package of the standard library.

![PyDoc](https://thepracticaldev.s3.amazonaws.com/i/8oqj5adkdycg8t703qkx.PNG)

I'm not going to bother discussing how ugly it is, because it's beside the point, but it's just so barebones. There are links to the functions and all, but that's about it. Let's compare that to Ruby's `yard` gem.

![Yard Example](https://thepracticaldev.s3.amazonaws.com/i/ohpml1wu1qdabmhfxgc4.PNG)

To me, the difference is night and day. Ruby has dynamic typing as well, but the documentation tools are so good, with support like official typing notation and a side navbar tree, that it makes it so much easier to work with. That's a big deal when working with other people.

To be clear, there may very well be better documentation tools for Python out there; in fact, I'm sure there are. But Python's default tools are what most people are going to use, and if the default tools are bad, the documentation is likely to be as well.

**Edit [1/15/20]:** I recently played around with Doxygen, which was a lot better than the built-in tools, and can get the job done to a decent extent. Still, due to the dynamic typing, it's a lot more work than it would be with static typing.

# **Bad:** Poor Dependency Management

I'm not a fan of the way Python handles dependencies at all. For whatever reason, "wheel files" were created, which is a format for Python dependencies. The problem with them is that they are sometimes tied to specific architectures or Python versions. For example, if I download a wheel file for Python 3.7, and you have Python 3.5, oh well, you can't use it! I get version compatibility, but this goes beyond that. You must have the exact specifications to install certain wheel files. For people working in an offline environment, this is very frustrating. They can't simply keep a folder of the dependencies they need for their offline machine since they could easily be useless if they update my Python version.

The other option is to download the archived source code, and that tends to work pretty well. It doesn't require specific versions of Python or architectures. It works well enough that you may wonder why wheel files exist. From my perspective, it feels like somebody said: "Ya know, archived source files work great, but why don't we make it harder for the developers to work with dependencies instead?" Maybe I'm missing something, but no other language that I've dealt with has that problem.

But what am I complaining about if I can just use the tar files, then? Well, it turns out that some package developers just don't offer them. Of course, you can always go to the GitHub repos where the source is, but you then run into issues with different directory structures and a lack of official releases like PyPi has.

# **Bad:** Not Very Portable

Python is not alone in this problem since it's mostly just a problem with interpreted languages in general. The issue is that when you want to send your Python code to anybody else to use, it takes a few steps.

For example, if you used any third-party dependencies, the other person will have to download them themselves too. You're not going to make something in Python and have your computer-illiterate friend use it like you would be able to do in a compiled language.

Portability is a particular problem for folks in offline environments, where every dependency change and deployment is a huge deal. It'd be much more convenient to have the dependencies bundled with each other, which is possible through a process called "freezing", but I've had tough luck with freezing Python code in more cases then I care to talk about, where it either required internet connectivity where I had none, or the resulting executable just doesn't work.

# Conclusion

There was a time when I loved Python. When was that time? It was when I only knew C++. *Of course* Python's going to look great if that's the only language you know. It was easy, and it did the work for you. But in a real, practical, everyday environment, it just doesn't fly for me. There are just too many better options for languages to start with that fix the problems Python has.

I hope it's obvious that the "Good", "Bad", and "Meh" labels are my opinion; I'm not trying to pass those off as fact. If you disagree with anything, let me know! I'd enjoy discussing it. And if you noticed that I said anything that is objectively wrong, post a comment! I'm open to having my mind changed.

Thanks for reading!