in this article, I review some methods and tips for you to become a better Python developer.

Come on, let’s start with the easy…

Avoid global variables

A local variable is better than a global variable concerning the memory occupation. So if you have variables to declare, do it in your functions rather than out of them unless you really have no choice.

For example, rather than this:

x = 5
y = 8

def add():
    return x + y

print(add())

Prioritize this:

def add():
    x = 5
    y = 8
    return x + y

print(add())

It doesn’t look like much, but it’s still a bit of performance that can be scrounged. And we all know that the performance gain is very important, especially in Python, so these little things should never be neglected.

The slotted classes

When you create objects from a class, you can also make them take up less memory. This can be done by creating so-called “slotted” classes. This consists of explicitly defining the set of fields that our class is allowed in the __slots__ field rather than in a __dict__ field.

class Exempleslots():
    __slots__ = ['name', 'age']

    def __init__(self, name, age):
        self.name = name
        self.age = age


x1 = Exempleslots('Raymonde', 36)
x2 = Exempleslots('Korben', 40)
print(x1.name)
print(x2.age)

Beyond saving memory, it also offers faster access to the attributes of the class and it also allows you to no longer make mistakes in the attributes that you add to your class. If you have a program that requires the creation of many instances of the same class, this will allow you to optimize its performance, make real memory savings and, incidentally, avoid dumping.

Always use Python’s functions and libs

It is important to know a language well, because in general, everything is already present in it. Let’s say I have a list of words I want to join. This is perfectly possible with a “for” loop like this:

s = " "
for word in words:
    s = s + word + " "
print(s)

But you might as well use Python’s “join” function:

words = ["hello", "world", "my", "name", "is", "Korben"]
print(" ".join(words))

Fewer lines, less time, less risk of errors. This is only a very small example, but it is a reflex that you must have. When you do assessment tests after finding the business of your dreams , you will make the difference with these kinds of small quality details.

Leverage data classes

A data class is an ordinary class that is created specifically to hold data. To create it, you will need the decorator @dataclass. Decorators are functions that modify the behavior of other functions without affecting their basic functionality.

Here is an example :

from dataclasses import dataclass
@dataclass
class Dog:
    name: str
    age: int
    owner: str

dog1 = Dog("Medor", "4", "Mike")
dog2 = Dog("Sultan", "2", "Korben")

print(dog1 == dog2)
#False
print(dog1.name)
#Médor
print(dog1)
#Dog(name='Medor', age='4', owner='Mike')

Also, the data classes you create will be comparable thanks to the “eq” method already present in the @dataclass.

This will add a lot of functionality to your class that will be inherited from the decorator, allowing you to have a very compact class. This class will not contain functions, only fields with your data.

Add an “else” after your loops

When you make a loop that should run until a condition is met, you use a variable like “test=True” when the condition is met and you break the loop with a “break” like this, then you chain on an “if test…”:

for i in range(6):
    print(i, i*i)
    if i == 7:
        test = True
        break
    else:
        test = False

if test == False:
    print("test is false")

Although less usual, a better syntax would rather be to use an else after your “for” or “while” loops like this to make your code more concise:

for i in range(6):
    print(i, i*i)
    if i == 7:
        break
else:
    print("test is false")

Merge dictionaries

If you are using data dictionaries and want to merge them, in a fairly natural way, we usually do this:

dict1 = {'name':'John', 'age':'25'}
dict2 = {'height':'193', 'weight':'100'}

#fusion dans dict3
dict3 = dict1.copy()
dict3.update(dict2)

print(dict3)

But with the arrival of Python 3.9 and above, it’s even easier. Just a little “pipe” like this:

dict1 = {'name':'John', 'age':'25'}
dict2 = {'height':'193', 'weight':'100'}
dict3 = dict1 | dict2
print(dict3)

Always remember to find out what new versions of your favorite language can do. It’s a bit like your job search. Think well when you fill in your talent.io profile to stand out by highlighting your strengths and your differentiating elements so that recruiting companies stop on your profile and be convinced that you are the best.

Use tuples

In Python, beginner developers tend to create lists when it comes to storing data. But have you considered using a tuple instead? This shares substantially the same characteristics as a list, except that the tuple is immutable (it cannot be modified once defined) and its syntax is different (parentheses are required).

This is what a list looks like:

test_scores = [100, 90, 70, 60, 50, 40, 30, 20, 10, 0]

And this is what a tuple looks like:

test_scores = (100, 90, 70, 60, 50, 40, 30, 20, 10, 0)

Then, it suffices to compare the size in memory of each of the objects to realize that the tuple eats up less memory space:

import sys

list_test_scores = [100, 90, 70, 60, 50, 40, 30, 20, 10, 0]
tuple_test_scores = (100, 90, 70, 60, 50, 40, 30, 20, 10, 0)

print(sys.getsizeof(list_test_scores))
# taille de la list : 136
print(sys.getsizeof(tuple_test_scores))
# taille du tuple : 120

As you will have understood, if you have not planned to modify a list, you might as well use a tuple.

As you can see in this example, I also used the sys.getsizeof() function which measures the memory usage of a Python object. This will allow you to benchmark the memory usage of your code and optimize it as you go. In general, always try to reduce the number of lines and the memory footprint of your program. Also try to reduce the time spent looking for a job, and aim directly for quality .

Fill out a list

If I ask you to fill a list with odd numbers from 0 to 100, you’ll probably use the “for” function like this:

mylist = []
for i in range(1,101):
        if i%2 != 0:
                mylist.append(i)
print(mylist)

However, there is another way to populate a list without a for loop using the “comprehension” method like this:

mylist2 = [i for i in range(1,101) if i%2 != 0]

A shorter, more efficient syntax in which you can even add functions if you wish:

def ma_fonction(x):
    return x % 2 != 0

mylist3 = [i for i in range(1,101) if ma_fonction(i)]

Consider using itertools

The itertools are a lib that offers ready-made iteration blocks. This allows you to perform common operations quickly.

For example to make permutations of letters, we can use “permutations” or combinations like this:

import itertools

print(list(itertools.permutations("KORBEN")))
print(list(itertools.combinations("KORBEN", 3)))

Here’s the result :

Permutations : 

[('K', 'O', 'R'), ('K', 'O', 'B'), ('K', 'O', 'E'), ('K', 'O', 'N'), ('K', 'R', 'O'), ('K', 'R', 'B'), ('K', 'R', 'E'), ('K', 'R', 'N'), ('K', 'B', 'O'), ('K', 'B', 'R'), ('K', 'B', 'E'), ('K', 'B', 'N'), ('K', 'E', 'O'), ('K', 'E', 'R'), ('K', 'E', 'B'), ('K', 'E', 'N'), ('K', 'N', 'O'), ('K', 'N', 'R'), ('K', 'N', 'B'), ('K', 'N', 'E'), ('O', 'K', 'R'), ('O', 'K', 'B'), ('O', 'K', 'E'), ('O', 'K', 'N'), ('O', 'R', 'K'), ('O', 'R', 'B'), ('O', 'R', 'E'), ('O', 'R', 'N'), ('O', 'B', 'K'), ('O', 'B', 'R'), ('O', 'B', 'E'), ('O', 'B', 'N'), ('O', 'E', 'K'), ('O', 'E', 'R'), ('O', 'E', 'B'), ('O', 'E', 'N'), ('O', 'N', 'K'), ('O', 'N', 'R'), ('O', 'N', 'B'), ('O', 'N', 'E'), ('R', 'K', 'O'), ('R', 'K', 'B'), ('R', 'K', 'E'), ('R', 'K', 'N'), ('R', 'O', 'K'), ('R', 'O', 'B'), ('R', 'O', 'E'), ('R', 'O', 'N'), ('R', 'B', 'K'), ('R', 'B', 'O'), ('R', 'B', 'E'), ('R', 'B', 'N'), ('R', 'E', 'K'), ('R', 'E', 'O'), ('R', 'E', 'B'), ('R', 'E', 'N'), ('R', 'N', 'K'), ('R', 'N', 'O'), ('R', 'N', 'B'), ('R', 'N', 'E'), ('B', 'K', 'O'), ('B', 'K', 'R'), ('B', 'K', 'E'), ('B', 'K', 'N'), ('B', 'O', 'K'), ('B', 'O', 'R'), ('B', 'O', 'E'), ('B', 'O', 'N'), ('B', 'R', 'K'), ('B', 'R', 'O'), ('B', 'R', 'E'), ('B', 'R', 'N'), ('B', 'E', 'K'), ('B', 'E', 'O'), ('B', 'E', 'R'), ('B', 'E', 'N'), ('B', 'N', 'K'), ('B', 'N', 'O'), ('B', 'N', 'R'), ('B', 'N', 'E'), ('E', 'K', 'O'), ('E', 'K', 'R'), ('E', 'K', 'B'), ('E', 'K', 'N'), ('E', 'O', 'K'), ('E', 'O', 'R'), ('E', 'O', 'B'), ('E', 'O', 'N'), ('E', 'R', 'K'), ('E', 'R', 'O'), ('E', 'R', 'B'), ('E', 'R', 'N'), ('E', 'B', 'K'), ('E', 'B', 'O'), ('E', 'B', 'R'), ('E', 'B', 'N'), ('E', 'N', 'K'), ('E', 'N', 'O'), ('E', 'N', 'R'), ('E', 'N', 'B'), ('N', 'K', 'O'), ('N', 'K', 'R'), ('N', 'K', 'B'), ('N', 'K', 'E'), ('N', 'O', 'K'), ('N', 'O', 'R'), ('N', 'O', 'B'), ('N', 'O', 'E'), ('N', 'R', 'K'), ('N', 'R', 'O'), ('N', 'R', 'B'), ('N', 'R', 'E'), ('N', 'B', 'K'), ('N', 'B', 'O'), ('N', 'B', 'R'), ('N', 'B', 'E'), ('N', 'E', 'K'), ('N', 'E', 'O'), ('N', 'E', 'R'), ('N', 'E', 'B')]

Combinaisons :

[('K', 'O', 'R'), ('K', 'O', 'B'), ('K', 'O', 'E'), ('K', 'O', 'N'), ('K', 'R', 'B'), ('K', 'R', 'E'), ('K', 'R', 'N'), ('K', 'B', 'E'), ('K', 'B', 'N'), ('K', 'E', 'N'), ('O', 'R', 'B'), ('O', 'R', 'E'), ('O', 'R', 'N'), ('O', 'B', 'E'), ('O', 'B', 'N'), ('O', 'E', 'N'), ('R', 'B', 'E'), ('R', 'B', 'N'), ('R', 'E', 'N'), ('B', 'E', 'N')]

The itertools (or iteration tools) are really very practical and I invite you to consult this documentation page which reviews all the features of this essential lib.

Move calculations out of loops

Still for the sake of optimization, if you are iterating using a “for” loop to, for example, test the validity of an email using a regular expression, remember to output the loop regex.

For example, if you do like this:

import re

emails = ["korben@korben.info", "raymonde@korben.info", "jp@korben.info", "youpi%korben.info"]
for i in emails:
        if re.match(r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$', i):
                print(i + " is a valid email")
        else:
                print(i + " is not a valid email")
                

You can do more optimized like this:

import re
email_regex = re.compile(r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$')

emails = ["korben@korben.info", "raymonde@korben.info", "jp@korben.info", "youpi%korben.info"]
for i in emails:
        if re.match(email_regex, i):
                print(i + " is a valid email")
        else:
                print(i + " is not a valid email")
                

Another good time saver in the performance of your code.

think differently

Imagine you have to code a developer who attacks his Monday morning. Here is the algo: “  If I have the Internet and I have a coffee in my hand, then I can work. Otherwise, I put myself in the lateral safety position. ” 

Translated into code, of course it would look like this:

if Internet() is True:
        if Coffee() is True:
                print("Je me mets au travail")
else :
        raise Exception("Je me mets en PLS")

But with a little thought, we can return this condition like this and save a few lines:

if (not Internet()) or (not Coffee()):
        raise Exception("Je me mets en PLS")
print("Je me mets au travail")

Obviously, I hope that during your next interviews, you will not mention this story of the lateral safety position ;-))). However, I am sure that your original way of thinking will be highly appreciated when companies browse your profile .

Conclusion

Well, I hope you enjoyed this little overview for experienced Python developers. Keep in mind that doing better is always possible when it comes to IT development. There is always a way to go further in your thinking and optimizations, and it is precisely this that will make the difference between you and other candidates when looking for your next job.

5/5 - (2 votes)

About the Author

SAKHRI Mohamed

Founder & Editor

Passionate about the web, new technologies and IT, I share on easy-tutorials.com tutorials, tips, advice, online tools and software for Windows, Mac and Linux.

View All Articles