How to Write Efficient Code in Python, Fast
2023 is upon us, meaning it's the perfect time to up skill and learn something new. Well, it's not just a new skill... it's one of the best coding skills you can learn. That's right, you guessed it: Python!
Why Python?
Python is a high-level yet powerful programming language.
You don't need to stick to the types, manage memory, or define classes here and there to write code in Python. Its syntax is plain and straightforward, all while supporting functional and object-oriented programming paradigms. Alongside this, it is portable and compatible with different CPU architectures.
You don't need to hold a Computer Science degree to start coding in Python either. Moreover, for its simplicity and flat learning curve, Python is commonly used by people from different areas to solve various engineering problems.
Yet you can benefit from being curious about how Python works under the hood to leverage it fully. In this post, I want to share some tips and tricks I use in my daily programming in Python.
1. Be careful of what you pass to a function.
Python doesn't allow developers to explicitly state how they like to pass arguments to a function. Instead, the Python interpreter decides whether to give value or reference based on the argument's type.
There are two possible object types in Python: immutable and mutable.
All primary data types, like strings and integers, are immutable. It means that any change to an original object causes the creation of a new object with a new address in memory.
The interpreter always passes immutable arguments by values. They are copied into a stack when a function is called. All changes made inside the context of a function do not affect the original objects since only their copies are being modified.
Mutable data structures, like lists or dictionaries, are passed by reference. Reference can be known as a new name for the same object. We can have many different words for the same object, but when calling these names to update them, they will translate directly to an original.
When dealing with mutable types in a function, think twice about whether you want to modify or copy an original argument.
2. Use literals to set up built-in types.
A literal is something that is defined in the specification of the language's syntax that creates certain data types
In Python, one can set up new objects of primitive data type using literal notation or a constructor of built-in classes. Consider the difference between “” and “str()”, “[]” and “list()” or “{}” and “dict()”.
Literals usually run faster. In Python, built-in class constructors might be overloaded just like any other functions in your code. The interpreter needs to check that you call the built-in constructor to initiate a new object, not some custom implementation with the same signature.
Checks like these slow down the speed of your code. On the other hand, Python does not allow you to overload string, literal number syntax, or any literal syntax.
3. Concatenate strings smartly.
In Python, we can concatenate strings by using an overloaded plus sign, i.e. "+" or a built-in "join()" function. Being immutable type, strings are constantly recreated in memory when we assign a new value to them.
Python interpreter first copies all the content from the original string, adds whatever will be added to it, and finally allocates new space for it in memory. This is what happens every time you join strings with "+".
Looks alright for a one-time thing. But guess how many times the word "Python" will be copied if we were to concatenate "Python", "is" and "fun!"? A whole three times!
But we can do better than this. Using "join()" lets Python pre-calculate the size of the resulting string, allocate enough space for it and fill it with content. All in one go. Getting rid of intermediate results makes it both time and memory efficient.
4. Use decimals for precise calculations.
In Python, there are three options to choose from when it comes to floating point number types: float, double and decimal.
The choice depends on a use case, but if you were writing a Python program for a bank and were using float data type to store dollars and cents, you probably would be deeply disturbed to discover that $262,144.00 is the same as $262,144.01.
Try to convert these numbers to a binary format, and it becomes clear that accuracy is defined by the number of bits available to store the significant. Float provides the least possible precision, roughly the equivalent of 8 digits. This is why Python, by default, uses double for floating point arithmetics.
Decimals, in turn, have a fixed-point precision, defaulting to 28 digits, that is user-alterable and can be as large as needed for a given problem. This feature makes decimals so commonly used in financial applications for currency amounts, interest rates, etc.
5. Ask for forgiveness, not permission.
As our CTO, Tom Trolez, loves to say, "It is always better to ask for forgiveness than for permission."
The same principle applies to code. Instead of making the Python interpreter calculate every branch of a nested if-else statement to check the input correctness, wrap your code in a try-catch. It will save time, considering that your code executes successfully more times than it fails.
As a final note.
There is always room for improvement, and one can keep chasing efficiency until nothing is left but a sequence of 0 and 1. That is not reasonable.
According to Moore's law, machines are getting more sophisticated annually, changing programmers' approach from "writing code for computers" to "writing code for humans". And Python is a perfect example of this change.