8 new anti-patterns for Python

Freshly added in DeepSource's Python analyzer.

Header image
By Srijan on 
Share on Twitter Share on LinkedIn

Making analysis results more useful and relevant is one of our primary goals at DeepSource, and we are regularly adding more capabilities to our core analyzers. After latest update, the Python analyzer now detects 8 new anti-patterns, which can help you avoid some common gotchas. Take a look!

1. Not using with to open files

When you open a file without the with statement, you need to remember closing the file via calling close() explicitly when finished with processing it. Even while explicitly closing the resource, there are chances of exceptions before the resource is actually released. This can cause inconsistencies, or leading the file to be corrupted. Opening a file via with implements the context manager protocol that release the resource when execution is outside of the with block.

Bad:

new_file = open('some-file.txt', 'r')
# do something exciting
new_file.close()

Good:

with open('some-file.txt', 'r') as fd:
    data = fd.read()
  # do something exciting

2. Having type information in a variable name

Developers struggle with naming variables, of course. Sometimes it may happen that you add the type information in the variable name, which can be confusing for other developers, or even your future self, who can act on the variable assuming that it is of the type mentioned in its name. It is recommended to give meaningful names to variables suiting the context.

Bad:

fruit_list = ('Apple', 'Banana', 'Orange')

# mistakingly assuming that fruit_list is a list
fruit_list.append('Mango')

Good:

fruits = ('Apple', 'Banana', 'Orange')

3. Returning more than one object type in function call

Having inconsistent return types in a function makes the code confusing and complex to understand, and can lead to bugs which are hard to resolve. If a function is supposed to return a given type (e.g. integer constant, list, tuple) but can something else as well, the caller of that function will always need to check for the type of value being returned. It is recommended to return only one type of object from a function.

If there’s a need to return something empty in some failing case, it is recommended to raise an exception that can be cleanly caught.

Bad:

def get_person_age(name):
    person = db.get_person(name)
    if person:
        return person.age  # returns an int

    # returns None if person not found

Good:

def get_person_age(name):
    person = db.get_person(name)
    if not person:
        raise Exception(f'No person found with name {name}')
    return person.age  # guaranteed to return int every time

4. Not using get() to return default values from a dictionary

This anti-pattern affects the readability of code. Often we see code to create a variable, assign a default value to it, and then checking the dictionary for a certain key. If the key exists, then the value of the key is assigned into the value for the variable. Although there is nothing wrong in doing this, it is verbose and inefficient as it queries the dictionary twice, while it can be easily done using the get() method for the dictionary.

Bad:

currency_map = {'usd': 'US Dollar'}

if 'inr' in currency_map:
  indian_currency_name = currency_map['inr']
else:
  indian_currency_name = 'undefined'

Good:

currency_map = {'usd': 'US Dollar'}
indian_currency_name = currency_map.get('inr', 'undefined')

5. Not using items() to iterate over a dictionary

The items method on a dictionary returns an iterable with key-value tuples which can be unpacked in a for loop. This approach is idiomatic, and hence recommended.

Bad:

for code in country_map:
    name = country_map[code]
    # do something with name

Good:

for code, name in country_map.items():
    # do something with name
    pass

6. Class containing only fields and crude methods

Classes that only hold data and do not contain any other functionality can not operate independently. It is recommended to use structures meant for holding data instead, like namedtuples, data classes, etc.

Bad:

class Person:
    def __init__(self, first_name, last_name, age):
        self.first_name = first_name
        self.last_name = last_name
        self.age = age

Good:

from collections import namedtuple

Person = namedtuple('Person', ['first_name', 'last_name', 'age'])

Read more about namedtuple here.

7. Presence of getter or setter methods in a class

For every class member that the has to be exposed publicly, developers sometimes define a get and set method. Getters and setters are used in many object oriented programming languages to ensure the principle of data encapsulation, but is frowned upon in Python as a waste of time and a cause of unnecessary code.

In Python, this can be done using the built-in property decorator in place of a getter, and <attribute>.setter decorator in place of a setter.

Bad:

class Dog:
    def __init__(self, name):
        self.__name = name
    def get_name(self):
        return self.__name
    def set_name(self, name):
        # do some processing on `name`
        self.__name = name

Good:

class Dog:
    def __init__(self, name):
        self.name = name

    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self, name):
        # do some processing on `name`
        self.__name = name

8. Pushing debugger in production code

Most of us have done this at least once — while debugging code, it may happen that you push your code after you found the bug but forgotten to remove the debugger. This is critical, and can affect the behavior of the code. It is highly recommended to audit the code to remove invocations of debuggers before checking in.


We’re actively adding more anti-patterns to our analyzers. What other anti-patterns would you like to see? Let us know @DeepSourceHQ.

Ready to free up your code base of these anti-patterns?
DeepSource flags hundreds more of similar anti-patterns in your code.