Why I Hate Python

Why I Hate Python

·

23 min read

Python is a popular and easy. Though being readable and modular, extensible in C++, there were some pitfalls felt. This article is all about my bad experiences with Python.

Table of Contents

I. Python Intro

II. Blog Intro

  1. White Spaces
  2. Pythonic Way of Stuff
  3. Dynamic Typing
  4. Manifest Typing
  5. Global Scoping and Life
  6. List of Other Issues

III. Conclusion



Python Intro

python-logo-master-v3-TM-flattened.png

Python is powerful... and fast; plays well with others; runs everywhere; is friendly & easy to learn; is Open. ~python.org

Python interpreted, high-level and general-purpose programming language. Python's design philosophy emphasizes code readability with its notable use of significant whitespace.

Python is created by Guido van Rossum and first released in 1991.


Blog Intro

According to Guido van Rossum

"high-level programming language, and its core design philosophy is all about code readability and a syntax which allows programmers to express concepts in a few lines of code."

and he is right. Python reflects this very well. Python is great for Machine Learning, Web Development, Statistics and Analysis, Data Science, and so on. Python also has a vast source of libraries and packages to use decreasing the development time.

Python is a popular and easy. Though being readable and modular, extensible in C++, there were some pitfalls felt.

It's not only Python but any language in this case. A toolbox contains different tools that perform a specific task. Programming languages are also tools in a developers pocket. Every language ever created was to simply certain aspects of programming. For example, Fortran, R, Scala are great for statistics and analysis. Java, C# for application development and Basic, C maybe for OS. Each language has its purpose.

A Programming Language does not define who we are. The use of it does.

There is no best programming language. It is just a preference based on coding style and needs.

Also the developer has a certain preference towards a language on the taste. Some may like Tab Indentation and some Brackets. Some prefer Strictness and some Liberty.

The same way I do have some preference over some features and concepts that other languages provide than Python. This series is all about those concepts and some workarounds for them.

P.S. : I don't hate it, just I am not such a fan as others seem to be.


1. White Spaces

In Python, indentation is used to delimit blocks of code. Many languages use curly braces {} to define block of code such as Java, C. Others use Begin/End like Visual Basic or SQL itself.

Python uses whitespace and colon to indent statements that define a block of code.

When Python encounters a problem with the indentation of your program, it either raises an exception called IndentationError or TabError.

Python’s use of indentation comes directly from ABC, but this idea didn’t originate with ABC--it had already been promoted by Donald Knuth and was a well-known concept of programming style. However, ABC’s authors did invent the use of the colon that separates the lead-in clause from the indented block. After early user testing without the colon, it was discovered that the meaning of the indentation was unclear to beginners being taught the first steps of programming. The addition of the colon clarified it significantly: the colon somehow draws attention to what follows and ties the phrases before and after it together in just the right way. ~Guido van Rossom

What Python sees with White Spaces?

Python maintains a stack to track indentation. The stack is used to define the block of code. This is a concept in any programming language.

In Python before the first line of the file is read, a single zero is pushed on the stack. The 0 index will not be popped. The indentation level pushed on the stack is always increasing from bottom to top. The line's indentation level is compared to the top of the stack and if it is equal, nothing happens else if it is larger, it is pushed on the stack, and one INDENT token is generated. When indentation number is smaller, it will be a number already on the stack. All numbers on the stack that are greater are popped and for each number popped a DEDENT token is generated. At the end of the file, a DEDENT token is generated for each number remaining on the stack that is larger than zero.

Why I dislike this?

The big caveat is inconsistency in White Space. The Python 3 compiler explicitly rejects any program containing an ambiguous mixture of tabs and spaces, usually by raising a TabError. This is difficult to debug when you don't use a smart Editor or an IDE.

def this_function_prints_two_statements():
    print("Hello World")  # Indented by space (\n)
    print("Hello Hashnode")  # Indented by tab (\t)

Visually this is at the same indentation level but Python will result into TabError: inconsistent use of tabs and spaces in indentation

Second common error is that the Space is not even and with a large code base it is very difficult to find. Though Python is efficient in pin pointing where indentation is wrong I still miss {} and End Sub from C and VB. I have no workaround but you can invest in a smart editor. My recommendation is PyCharm by JetBrains.


2. Pythonic Way of Stuff

mandalorian-1604078974.jpg

Every language has its own coding style and conventions. Python has it too. Python community has accepted some patterns and conventions to developed unified, maintainable, readable and extensible Python application. The Pythonic way is applied every where from variable naming to package naming and even indentation. The code looks great and beautiful.

However some times I experience that the code becomes unreadable, totally opposite from what Python markets itself on.

What I Love about the Way

Python :
array = ["This", "Is", "The", "Way"]
is_mandalore_found = "Mandalore" in array

This is great. Highly readable compared to equivalent source in other Languages.

C :
char * array[] = {"This", "Is", "The", "Way"}
for (int i = 0; i < (sizeof array / sizeof *array); i++) {
    if (!strcmp(array[i], "Mandalore")) {
        found = true;
    }
}

Definitely Python code is smaller and highly readable. Note: for a new comer it is great but an experienced coder will definitely understand both.

What I Don't Like

Python :
array = [1, 2, 3, 4, 5, 6, 7]
for i in range(len(array)):
    if array[i] % 2 == 0:
        array[i] += array

Above source is readable for a newbie. It demonstrates the logic very well. Below is the Pythonic way. Complex for me as I don't know of list comprehension

Python :
array = [x * 2 if x % 2 == 0 for x in [1, 2, 3, 4 5, 6, 7]]

Also the flow of the logic is lost. Simplicity is lost. How you read the above code is from right to left. Not common in English.

Also the Pythonic Way is not consistent among Python Users. Experienced Python Uses. The reason is the Way it self. Make everything readable. Some believe the logical way is better and some believe the code should be short no matter what.

When a Pythonista comes across a statement in Python he would change it to the Way style to make it more readable and beautiful. A code that is working for years and tested a billion times is changed and now there is a risk of ruining the project. Also Python has a bad reputation with Errors. PEP8 will handle Python won't. More on this below.


3. Dynamic Typing

It's a myth that programmers don't have to worry about types.

Python is Dynamically and Strongly Typed language and it is great with its own advantages. But not Static and Manifest Typed such as C, C++, Visual Basic, C#, etc.

dynamic_typing.png

In Dynamic Typing the type of the object is assigned at runtime. In dynamically typed languages it becomes more hard to trace an objects type and thus duck typing the Pythonic concept come to save us. More on this below.

With dynamic typing one has to know what object they are working on and to find out they have to know every place the object is assigned some value, as it might change the type.

variable_one = 12.34
variable_one = "Dynamic Typing"

No error is occurred here as it is totally possible. At run time first it is assigned of type integer. Then it is assigned of type string.

Note : Strong Typing is a different topic. It means that explicit type casting should be declared when two different datatypes are used in an expression.

In Static Typing the type of the object is predefined and the type will not change through out the life of the object. And so it becomes easy for one to use the object. If Python was Static Typed :

variable_one = 12.34
variable_one = "Dynamic Typing"  # Error: TypeError: Object is not of appropriate type.

Benefits of Static Typing includes :

  1. Documentation : As types are predefined with its signatures it is easy to get the information about the object. In Python, type hints are used. Comments are used to document which is pretty tedious. And This is not always true as false positives can occur during runtime.
  2. Faster Execution : Statically typed code can also run faster. It doesn’t matter if the language is interpreted or compiled; either way, knowledge of static types can allow for additional machine code optimization, so your program isn’t using more memory or clock cycles than it really needs.
  3. Better error messages : In a dynamic language, the you won't find the exact line when error occurs and debugging will be costly. One must look through all the lines of code and manually analyze error.
  4. Code analyzers : Linters can never be as good as what Static Language can do. Dynamic Languages use external linters such as PEP8 or PyLint or MyPy in Python.
  5. Object Type : When an unsure type is expected, most of the languages provide object type such as Object in VB, void in C, var, etc. So the advantage gained in Dynamic Type is also available in Static Typed languages.

Thus, Static Typed languages are usually go to languages for large projects as they are more specific and accurate with operations on objects.


4. Manifest Typing

I had issues with Manifest Typing in Python. Manifest typing is when you explicitly define a type to a object. Such as in C or Java. Advantages of Manifest Typing are similar to static typing.

In Manifest Typing you will have to declare a variable before use. The advantage is that the compiler will know before hand that a variable is meant to be used and all required type signatures will be loaded before hand making it faster to execute. Also typing in mistakes in variable names can be found and overall structure of the program is preserved.

I don't use a smart IDE. In the below code is_student_name_appropriate is a flag that states the students name is valid or not. Lets not get into Pythonic way of writing this please. As you see I made a spelling mistake in code and now is_student_naem_appropriate is a new object and Python will not give error. The loop will run forever. In smaller code based it is easy to change things as everything is visible. Not with a larger code base.

is_student_name_appropriate = False
while not is_student_name_appropriate:
    try:
        student_name = str(input("Enter your name : "))

        if not student_name.isalpha():
            raise ValueError

        is_student_naem_appropriate = True  # spelling mistake in typing name. 
    except ValueError:
        print(f'Error : {student_name} is not a valid name. Enter only alphabets (A-Z and a-z) in student name!')

The source is short. But what about a super large code base. Python will not give compile time errors. They believe its PEP8 and IDE's job. I guess.

Scoping and Life in Python is messed up and this error becomes very common when refactoring and code reviews occurs. Its the Pythonic Way to refactor. Make the code readable.

The code below will generate a compile time error preventing such errors. The compiler doesn't know of is_student_naem_appropriate object as only student_name and is_student_name_appropriate are declared for use. Saves a huge amount of debugging time.

C++ :
int is_student_name_appropriate = 1;
string student_name;
while (!is_student_name_appropriate){
    try{
        cout << "Enter Student Name";
        cin >> student_name;

        if (not student_name.isalpha()){
            throw student_name;
        }

        is_student_naem_appropriate = True  # spelling mistake in typing name. 
    }
    catch (string x){
        cout << "Error :" << x << "is not a valid name. Enter only alphabets (A-Z and a-z) in student name!";
    }
}


5. Global Scoping and Life

Though Global variable are big No-No, there are certain best suited cases to use them. And Python has some how messed up Global Scoping.

The Global Keyword

Global keyword in Python is used to reference the variable outside the scope if it exists or initialize one.

global1 = "Hello Global"

def func():
    global1 = "Hello Hashnode"
    print(global1)

func()
print(global1)


Output:
Hello Hashnode
Hello Global  # unexpected output

As you can see the output Hello Hashnode expected is not gained. Even though the variable names are same and in scope and life. This is because Python has messed up the scoping. To access the global1 variable, Global keyword has to be used in the code block's scope. Like below :

global1 = "Hello Global"

def func():
    Global global1
    print(global1)
    global1 = "Hello Hashnode"
    print(global1)

func()
print(global1)

Output:
Hello Global
Hello Hashnode
Hello Hashnode

How about the source below :

def func():
    Global global1
    global1 = "Hello Hashnode"
    print(global1)

func()
print(global1)  # How is global1 in scope?

Output:
Hello Hashnode
Hello Hashnode

Even the variable is not declared in the global scope it is being accessed. Here the Global global1 initializes a Global variable. Yikes 😱. The source structure cannot be defined if Python allows that which it some how does.

Public, Private, Protected

Python does not have access modifiers. Oh Come On!!!

And please adding underscores like _variable_name or __variable_name are not access specifiers. Your IDE hides them in IntelliSense but they are accessible.

I just can't believe why is this the case. The explanation on wiki is that

[Python] has limited support for private variables using name mangling. See the "Classes" section of the tutorial for details. Many Python users don't feel the need for private variables, though. The slogan "We're all consenting adults here" is used to describe this attitude. Some consider information hiding to be unpythonic, in that it suggests that the class in question contains unaesthetic or ill-planned internals. However, the strongest argument for name mangling is prevention of unpredictable breakage of programs: introducing a new public variable in a superclass can break subclasses if they don't use "private" variables.

From the tutorial: As is true for modules, classes in Python do not put an absolute barrier between definition and user, but rather rely on the politeness of the user not to "break into the definition."


6. List of Other Issues

  1. Duck Typing : If it walks like a duck and it quacks like a duck, then it must be a duck.
  2. Self Parameter : So obsessed with explicit. What next? Explicitly develop a ABI for your program. Though I still like the concept of self, it is just not intuitive. Could be better.
  3. Memory Management : Obviously due to Dynamic Typing. Thought not an issue as hardware is pretty cheap.
  4. Slow Speed : Extra checking due to Duck and Dynamic Typing. Not noticable.
  5. Multithreading : CPython interpreter does not support true multi-core execution via multithreading. The Python Global Interpreter Lock or GIL is a mutex that allows only one thread to hold the control of the interpreter. This feature is just to prevent race condition. Where is the explicit thing you do Python? Hello? Where did you go?
  6. Database Access : Not sure as I only use MySQL but many say that the Python's database access layer is found to be bit underdeveloped and primitive.


Conclusion

Python is a great language as very beginner friendly. As going with the Pythonic Way I guess the community is taking it too seriously. The Way is a good concept but the scope should be limited to the organization and people on the project rather than a global convention. Multithreading lock with GIL is actually great for thread safe C code running in the back. White space looks beautiful with PyCharm. I never use Global Variables and nor should you. Private is still bad. And Dynamic, Duck thing too.

All the above concepts are just preference of the programmer and the need of the application and should be like that. This should not discourage you from learning Python. Remember again :

A Programming Language does not define who we are. The use of it does.

There is no best programming language. It is just a preference based on coding style and needs.


Found some more :


You might have counter opinions on the points above. It would be great to discuss it out in the chat.

Thank you for reading!

I would love to connect with you on Twitter

Check out my other blogs on aagamsheth.com