The Good, The Bad and The Ugly - When coding in Python
I've been coding in Python in the last few years and I'm still learning about good practices to improve my code quality. Throughout my journey I've seen good, bad and ugly practices.
Here's a list of common mistakes when developing in Python.
Using import *
This is a very inefficient practice. If you only need a module you should import that module only. It will help with debugging and readability.
| Bad
from typing import *
| Good
from typing import Dict, Optional
Using type() to compare types
isinstance is usually the preferred way to compare types. It is faster and it considers inheritance.
name = 'Di Maria'
| Bad
if type(name) == str:
print('it is a Di Maria string')
| Good
if isinstance(name, str):
print('It is a Di Maria string')
else:
print('It is something else')
Not following PEP8
PEP8 is a document everyone learning Python should read. It provides guidelines and best practices on how to write Python code.
Fortunately some of the rules are incorporated to IDEs.
For example, if you write code that don't follow PEP8 guidelines, your IDE (in this case, PyCharm) will show you some ugly underlines:
Using 'in' to check if an element is contained in a large list
Checking if an element is contained in a large list using the in statement might be slow for large lists. Consider using a set or a bisect instead.
| Bad
list_of_players= ['Kylian', 'Leo', 'Alexis', 'Angel']
if 'Leo' in list_of_players:
print('GOAT found')
| Good
set_of_players= {'Batigol', 'Leo', 'Enzo', 'Julian'}
if 'Leo' in set_of_players:
print('GOAT found')
Not using get() to return default values from a dictionary
Usually, when working with dictionaries, you will need to check if a key exists in one of them.
While using the in statement might be your first reaction, you should use the more concise dict.get(key[, default]) built-in method from the Python Standard Library.
If the key exists in the dictionary then the value for that key will be returned. If it doesn't then the value specified as the second argument in the get() will be returned.
| Bad
bands = {'GEN': 'Genesis', 'PNK': 'Pink Floyd', 'MSE': 'Muse'}
if 'GNR' in bands:
name = band['GNR']
else:
name = 'undefined'
| Good
bands = {'GEN': 'Genesis', 'PNK': 'Pink Floyd', 'MSE': 'Muse'}
name = bands.get('GNR', 'undefined')
Do you spot the difference? A one liner solution, very pythonic.
Never using comprehensions (or using them all the time)
Comprehension offers a shorter syntax when you want to create a new sequence (list, dictionary, etc.) based on a sequence already created.
In fact I have a previous post on this topic.
Say you need to lower case a list of world champion players, you could do this in a loop:
| Bad
world_champs = ['MESSI', 'DIBU MARTINEZ', 'CUTI ROMERO']
lower_case = []
for champ in world_champs:
lower_case.append(champ.lower())
Or you can do this with a very sexy one-liner comprehension
| Good
lower_case = [champ.lower() for champ in world_champs]
Comprehensions are very useful, but don't overuse them! Remember the Zen of Python: "Simple is better than complex".
Not using a context manager when reading or writing files
This is a common anti pattern saw on different code reviews. Context managers in Python help facilitate proper handling of resources, to control what to do when objects are created or destroyed. This removes the overhead of handling the creation or deletion of objects.
| Bad
file_obj = open('abc.txt', 'r')
data = file_obj.read()
file_obj.close()
| Good
with open('abc.txt', 'r') as file_obj:
data = file_obj.read()
Ignoring Comments
Undocumented code is a nightmare. These are the people who may complain about it:
- You in 6 months when you'll forget why you wrote that line of code.
- any colleague of yours who take over the project.
Code should always be clear in what it's doing and comments should clarify why you're doing it. At the same time, be concise when you comment your code. When your code is self-explanatory, comments are not needed.
Not Updating Comments
Comments that contradict the code are worse than no comments at all. An outdated is misleading for everyone working on the code.
There's always time to update comments.
No exception type(s) specified
Not specifying an exception type night not only hide the error but also leads to losing information about the error itself. So it's better to handle the situation with the appropriate error rather than the generic exception.
| Bad
try:
5/0
except:
print('Exception')
| Good
try:
5/0
except ZeroDivisionError as e:
print(f'ZeroDivisionError: {e}')
except Exception as e:
print('Exception')
else:
print('No errors')
finally:
print('bye')
Over-engineering everything
You don't always need a class. Simple functions can be very useful.
Using single letter variable names
| Bad
l = [2, 3, 4, 5]
| Good
numbers = [2, 3, 4, 5]
Formatting with the + operator
Probably one of the first things we learn in Python is how to join strings with the + operator.
This is a useful yet inefficient way to join strings, the more strings you need the more + you'll use.
| Bad
variable_a = 'value'
print('this is variable_a value:' + variable_a + '')
| Good
variable_a = 'value'
print(f'this is variable_a value: {variable_a}'
The best part of of the the f-strings is that's not only useful for concatenation but has
References:
- https://tvkoushik.medium.com/common-python-anti-patterns-to-watch-out-for-9271d13a3f8e
- https://medium.com/geekculture/10-python-mistakes-that-tell-youre-a-nooby-359487f22c97
- https://docs.quantifiedcode.com/python-anti-patterns/
- https://towardsdatascience.com/18-common-python-anti-patterns-i-wish-i-had-known-before-44d983805f0f
- https://deepsource.io/blog/8-new-python-antipatterns/
Comments
Post a Comment