18 Common Python Anti-Patterns I Wish I Had Known Before
By: Ahmed Besbes
Tags: Best Practices, Programming, Tips, Python, Software Development

1 — Using non-explicit variable names

Your variable names should always be descriptive to provide a minimum context: a variable name should tell you in words what the variable stands for.

This makes the code easier to understand for other developers and easier to debug for you.

TIP 👉: don’t be afraid to use long variable names to be descriptive: modern IDEs such as VSCode and Pycharm provide fast autocompletion.

2 — 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’ll take over the project

Code should always be clear in what it’s doing and comments should clarify why you are doing it. At the same time, be concise when you comment your code. When your code is self-explanatory, comments are not needed.

TIP 👉: if you’re using VSCode, you can speed up generating docstrings with this extension that automatically generate a comment template for your classes and functions.

image by the author

3 — Forgetting to update comments

Comments that contradict the code are worse than no comments at all.

This happens more frequently than you think: when you’re in a rush and have to fix a broken production code, you’ll do it without finding the time to update the old comments.

An outdated comment is misleading for everyone working on the code.

There’s always time to update comments.

4 — Using CamelCase in function names

This is more of a convention: PEP 8 style guide recommends that function names should always be lowercase, with words separated by underscores.

5 — Not iterating directly over the elements of an iterator

This is a quite common anti-pattern. You don’t necessarily need to iterate over the indices of the elements in an iterator if you don’t need them. You can iterate directly over the elements.

This makes your code more pythonic.

6 — Not using enumerate when you need the element and its index at the same time

When you need to access an element and its index at the same time when iterating over an iterator, use enumerate .

7 — Not using zip to iterate over pairs of lists

zip is a useful built-in function that allows you to create a list of tuples from two iterators. the first element of each tuple comes from the first iterator, whereas the second element comes from the second iterator.

zip can be helpful if you want to iterate over two or more iterators at the same time.

8 — Not using a context manager when reading or writing files

When you use open without a context manager and some exception occurs before you close the file (closing the file is something you must remember when opening a file this way) memory issues could happen and the file might be corrupted along the way.

When you use with to open a file and an exception occurs, Python guarantees that the file is closed.

9 — Using in to check if an element is contained in a (large) list

Checking if an element is contained in a list using the in statement might be slow for large lists. Consider using set or bisect instead.

Here’s a comparison of the three methods:

Image modified by the author

→ source: StackOverflow

10 — Passing mutable default arguments to functions (i.e. an empty list)

Here’s a funny thing in python that may result in silent errors and obscure bugs: default arguments are evaluated once when the function is defined, not each time the function is called.

This means that if you use a mutable default argument (such as a list) and mutate it, you will and have mutated that object for all future calls to the function as well.

To avoid this issue, you can set the default argument to to None:

if the function with called multiple times with to set to None, create a new empty list and append the element to it each time

set to None, create a new empty list and append the element to it each time when you pass a list to to , you append an element to it. Since it's not the default function argument, this works well.

11 — Returning different types in a single function

When trying to handle special user inputs that may generate errors, you can sometimes introduce None as output. This makes your code inconsistent since your function now returns at least two types: the initial type you intended and the NoneType type.

This makes it hard to test and debug later.

Instead of returning None, you can raise an error and later catch it.

12 — Using while loops when simple for loops would do the trick

You don’t need to use a while loop if you already know the number of iterations beforehand.

13 — Using stacked and nested if statements

Stacked and nested if statements make it hard to follow the code logic.

Instead of nesting conditions, you can combine them with Boolean operators.

14 — Using global variables

Avoid global variables like the plague. They’re a source of many errors. They can be simultaneously accessed from multiple sections of a program and this may result in bugs.

The typical error that arises when using global variables is when a function accesses its values before another one needs to update properly.

15 — Not using get() to return default values from a dictionary

When you use get , python checks if the specified key exists in the dictionary. If it does, then get() returns the value of that key. If the key doesn't exist, get() returns the value specified in the second argument.

16 — Using try/except blocks that don’t handle exceptions meaningfully

Using a try/except block and ignoring the exception by passing it (for instance) should be avoided.

17 — proudly typing: from module import *

Imports should always be specific. Importing * from a module is a very bad practice that pollutes the namespace.

18 —Over-engineering everything

You don’t always need a class. Simple functions can be very useful.

Classes often have methods, which are functions that are associated with a particular class, and do things associated with the thing that the class is — but if all you want is to do something, a function is all you need.

Essentially, a class is a way of grouping functions (as methods) and data (as properties) into a logical unit revolving around a certain kind of thing. If you don’t need that grouping, there’s no need to make a class.

Thanks for reading 🙏

I hope this is was useful to you. If you know other popular python anti-patterns, feel free to point them out to me in the comments.

Happy hacking, until next time 👋

Photo by Karsten Winegeart on Unsplash

Resources:

New to Medium? You can subscribe for $5 per month and unlock unlimited articles — click here.