In our ongoing review of the most popular programming languages, we have already covered C#, C, and C++:
Our review concludes with Python, which has literally exploded in popularity in recent years, giving it the top spot in the index.
Popularity of the Python programming language over time. Source: www.tiobe.com
There are many factors behind this. We are experiencing a tremendous increase in interest in the language in the following areas:
Using Python in these areas makes complete sense, and I hope you will agree with me after reading this article.
Python differs from the languages discussed so far in virtually every respect. Once you try Python, you will find that every other programming language is difficult to use. It requires years of experience and is fraught with problems that Python pretty much does not possess.
Python is one of the easiest programming languages to learn. Its applications are versatile – from simple scripting to complex machine-learning environments. Thanks to the vast array of libraries that can be used by typing a single line in the console (or with a single click in the IDE), the programmer has a powerful arsenal of ready-made solutions at their fingertips from the very first minutes of working with the Python language. Within a few minutes, a range of programs can be written, including an HTTP server or a simple game with a graphical interface. For instance, after importing the appropriate module, just two lines of code display a pop-up window saying, “Hello world!”.
“Hello world!” program in Python
A program that displays a pop-up that says, “Hello world!”. It works on Windows, Linux, and many other systems, I would suspect. Requires typing an extra line in the console once: pip install PyMsgBox.
Since Python is an interpreted language, adding new lines of code to run a new version takes no time, and there is no need to compile it. A few hours is all it takes to learn and use the language effectively due to the simplicity of its grammar. At the same time, it supports many different programming paradigms, including object-oriented programming, letting you pick the programming style that ideally suits the task at hand.
Code is easily broken down into modules in Python, which undoubtedly contributes to the massive base of ready-made modules. When tackling a new problem, it’s always wise to double-check whether someone hasn’t already done it, as it may only take one line of code to import and re-use an existing working solution.
Python manages memory using Garbage Collection mechanisms, much like the .NET platform, thus easing the burden on the programmer. Moreover, it comes with tools for asynchronous1, programming, including mechanisms that implement both the Task-based Asynchronous Pattern (TAP) and Event-based Asynchronous Pattern (EAP) approaches. There is one feature that makes Python stand out among all the other languages mentioned in this article – dynamic typing, that is.
Dynamic typing, as opposed to static typing in most modern programming languages, means that the type of variables does not have to be known when coding the program. It’s determined at runtime, which impacts a lot on how code is written. You can, for instance, write a function that expects a specific type of variable but doesn’t enforce that type. By later extending this function to other types, you do not have to make changes to either the API or the parts of the code that are common to these types. You may even find that you don’t need to change anything at all. This is because, thanks to a typical Python programming style called duck typing2, we can take the opposite approach and extend other types’ functionality to meet our function’s needs3.
And this is just the beginning. We can, for instance, expect our type to have a ‘test’ function and deal with the situation where the type does not have it. This way, we do not constrain the list of types for which our functionality works correctly; we only handle exceptions. To achieve the same effect in other programming languages, you must create appropriate abstractions and algorithms that operate on them. In other words, it is not uncommon to rewrite a large chunk of code to make one type handle two types. It is possible that similar work needs to be done to address the third one, too. Such code extensions are trivial in Python language.
As if that weren’t enough, in many ways, Python treats variables and collections of variables (arrays, tuples, etc.) the same way. Instead of a single variable, you can pass an array of them. The same rules apply to what we return from a function. Again, most programming languages are very strict about what a function can return. No problems like this exist in Python. You can return whatever you want, whenever you want, without declaring in advance what a function will return. The type returned can also be derived from the function’s arguments and be different for each call to the function.
You don’t really have to worry about too many things when programming in Python. All you have to do is sit down and get started writing code. Need support for this or that? There’s probably a module for that. Need a generic function? No need for a series of classes, abstractions, or type definitions – you write what you need. And as the project gets more complex, you can wrap what you require into classes or modules and organize the code better. Importing your modules is, again, just one line. No thought is given to compilation, headers, and other ‘relics’ of statically-typed languages. There is a reason why beginner programmers are most likely to start with Python. It makes it very easy to learn what is most critical in programming – problem-solving skills. You don’t need a rigid, restrictive language that forces you to learn and use lots of unnecessary mechanisms before you write your first decent program. Its ease of use is by far the strongest magnet for attracting new programmers, thereby increasing Python’s popularity.
It’s hard to imagine how programming in languages with dynamic and static typing differs without having at least a smattering of experience with both. If you don’t have that experience, believe me when I say it varies dramatically. However, there is more to dynamic typing than just the advantages alone. Like an asterisk on an advertisement or contract – the disadvantages of it are hidden from the developer until they read deeper into the content.
Python is the most internally polarized language I know. What I mean by that is I could praise every essential feature and then add a ‘but’ and some sort of accusation.
Getting started is pretty straightforward, but as functionality is extended with modules, it becomes much more complicated. Simple projects can be written quickly, but complex ones are harder to maintain over time. The lack of compilation and many other aspects of working with a code interpreter are welcome. However, this comes at a significant cost to the performance of the program4. Dynamic typing makes it significantly easier to write new code quickly. However, it does so at the expense of static analysis. Several basic programming errors can be eliminated through this analysis.
Python allows you to pass anything as an argument to a function. The example on the left will allow you to write arbitrary things on the screen. However, its behavior is rarely as expected. In order to handle lists and collections correctly, we need to extend the code like the example on the right. On top of this, we would need to handle boundary conditions, such as handling missing arguments or handling functions. We have no way of limiting the types for which the user will use our function. We can only “suggest” the use by means of hints. We should therefore protect ourselves against its erroneous use. A lack of safeguards may cause an error, or, more seriously, the code will work contrary to expectations without reporting an error.
It is this last point that I find most frustrating of all5. The lack of a compiler in Python means that simple errors, such as typos in variable names, only come up when the interpreter gets to the wrong line of code. It is not difficult to imagine the significant problems this can cause. Let’s take a look at this example: if we have written a piece of code including error handling, the code responsible for this action will only be executed (which also means: checked) when an error occurs. If the error never occurs, we will not know whether we have made a typo or not – the same applies to any other branch of the code. Errors in the code responsible for error handling are critical insofar as tests do not usually cover them. At least not from the very beginning.
The code above presents a typical example of Python problems. In the example on the left, the typo on line 4 will not be highlighted by the IDE because “something” may have an “appen” function. There is no mechanism to tell in advance whether or not it has such a function. When run, this code will throw an error for both use cases even if, as intended, it should output ’10’ for the second. The example on the right, on the other hand, shows a more serious problem to me: the incorrect sum of types ‘str’ and ‘int’ on line 9 will not be caught because this line will never be executed – both function calls do not raise an exception. It is not known when this error will be discovered. In this case, its consequences are minor, but even such a seemingly “innocent” error in production can be problematic, as it can terminate the entire program.
This problem entails a number of consequences. If you are developing production code or a publicly-available module, you should test every path in it. This is not because the manager has set a 100% code coverage with tests as a project goal. Instead, by not running through every line of code in tests, we will not be sure that there are no fundamental errors in it. While the parser that analyzes the executing file will check for missing brackets, inverted commas, or similar problems in the structure of the code, there is no way to tell if there is a typo in a particular function call. Even the most expensive paid IDEs cannot perform thorough code analysis as a function may be called with a different, utterly unexpected type. Even tests that cover 100% of the code will not check it for every supported type.
In practice, this means significantly delayed error detection. On the one hand, this is a problem we should be concerned about; on the other hand, these bugs must occur in less frequently used program paths. Otherwise, we would have found them long ago. Thus, the issue lies in ensuring the code is complete or of high quality – it may be harder, or at least more difficult, compared to other programming languages, to guarantee that the code will work correctly, taking into account future applications and project developments.
On top of this, there is a problem due to the very error handling performed by the interpreter. When the Python interpreter finds a problem, it throws an exception. Again, another great feature of the interpreter, to which I must add a BUT – this feature is the bane of novice programmers. Suppose someone inadvertently overrides a piece of code with the typical beginner’s try-except form, which catches all possible exceptions. In that case, they may also notice an interpreter exception and handle it generically, suppressing the actual reason for throwing it. This takes error detection even further back in time.
Error handling is necessary because we do not usually assume that an error will occur when we write code. We also typically do not test such scenarios, at least not immediately. Dynamic typing and code interpretation make it significantly more difficult not only to guard against errors but also to recognize their causes when they do occur.
Other programming languages also suffer from similar problems, but not to this extent. The reason for this is another difference in the style of working with Python – most projects written in statically-typed languages follow the LBYL (Look Before You Leap) practice, which, in a nutshell, tries to limit exception handling to genuinely exceptional situations and mandates verification of the initial requirements of various functions and modules. Python adheres to the EAFP (Easier to Ask for Forgiveness than Permission) doctrine, which says exactly the opposite – don’t verify; try to do what you want. You will, at most, cause an exception, which will be handled. The same approach should also be expected of external modules used in a project. This consequently means that it is much more common to have to deal with exceptions of various types.
The increased number of exceptions entails a desire to have it easy on the code at one point or another, leading to generic try-excepts that sometimes cover too wide a range of exceptions thrown, thus hiding various relevant errors in the code.
After reading the previous chapter, one might conclude that working with Python is all about problems. It is, therefore worth pointing out that the author of this article works with statically-typed languages on a daily basis. Hence, the differences in approach and errors he encounters are more apparent to him than to someone who has been programming Python for years.
It cannot be said that dynamically-typed Python is better or worse than statically-typed Python – they are just different. It has other advantages, different problems, and, consequently, different applications. You won’t find high-performance demanding games6, in Python language, nor complex numerical algorithms or machine learning. Anything that requires high computational complexity is written in dedicated languages, and yet you can write a game in Python for the Source engine, perform complex and labor-intensive matrix calculations using the NumPy library, or teach neural networks using the Tensorflow or PyTorch environment. Some Python development communities will create a suitable interface if the project is widespread. If the tool in question allows the user to add their code, the user may write it in Python.
|Scientific and business areas||Back-end||Other|
The general-purpose nature of Python makes it suitable for a wide variety of applications. However, it is most readily used where programming is merely a tool to achieve a much more complex goal, such as in scientific environments.
You may find it hard to believe, but the four languages Python, C, C++, and C# work well together. I have twice (in two different companies) encountered a project in which all four languages were used simultaneously – for very different purposes, of course. Both projects were embedded software projects, where the device driver layer was written in C, the application layer in C++, and a desktop application was created in C# to communicate with the device (management, configuration, debugging, updates, etc.), and Python was used for functional testing7.
Many projects seek to make the most of the diversity offered by different programming languages. As I see it, language choice was based on the team’s experience and capabilities for each of the tasks in the mentioned projects.
Nowadays, it is increasingly difficult to find a project written in one language, and very narrow specialization is starting to be limiting for programmers. It is, therefore, worth broadening your horizons and seeking out other languages to master. The TIOBE Index, which we have used as a guide in this series of articles, is a reliable reflection of the job market. In order to determine the potential future direction, we recommend more surveys published annually by StackOverflow8. They contain plenty of details and highlight current trends among developers in this vast and diverse community. They show, among other things, slightly different proportions when it comes to the four languages discussed in this series. C and C++ are rather marginalized, while Python and the .NET platform are gaining more and more fans. Python is, without a doubt, a significant player in the programming language market today, and this situation is unlikely to change anytime soon.