DEV Community

Jerry Ng
Jerry Ng

Posted on • Updated on • Originally published at jerrynsh.com

Ditch These 7 Bad Habits in Python

I have been guilty of all these bad habits. It took me some time to ditch them.

Python is well known for its simplicity and versatility. As one of the most used programming languages, there are a lot of great tutorials out there. Unfortunately, the easy-to-learn syntax can sometimes mislead developers, especially those who are new to the language. Writing clean Python code can be tricky.

In this article, we are going to dive into some of the common mistakes and bad habits that one might have picked up when they first started learning Python. This includes things that I wished someone would tell me earlier.

Don’t worry, I do not intend to bore you with a wall of text. Instead, you’ll be given code snippets that are easy to digest.

TL;DR

Stop:

  1. Using open and close instead of with
  2. Using an empty list as a default argument
  3. Avoiding list comprehension
  4. Abusing list comprehension
  5. Using bare exception
  6. Using == and is the wrong way
  7. Using import *

1. Using open and close Instead of with Statement

Working with file streams is probably the most common thing that we do as we started learning Python. Most of the tutorials would start off by telling us to open and close files using the example below:

f = open("stocks.txt", "r")
data = f.read()
f.close() # NOTE: Always remember to close!
Enter fullscreen mode Exit fullscreen mode

In such cases, we would often be reminded to always close the file after using it because:

  • It consumes our limited system resource
  • It prevents us from moving or deleting the files

Instead, the best practice here is to always use context manager (the with statement) in Python while dealing with file operations. Here's an example:

with open("stocks.txt", "r"):
    data = f.read()
Enter fullscreen mode Exit fullscreen mode

Using context manager here also helps us with closing the file during exceptions.

Well, you could argue that we could also use the try-finally approach here instead. Though, the with statement simplifies our code to just two lines.

2. Using an Empty List or Dict as a Default Argument

This is probably one of the more confusing gotchas for newcomers in Python.

I must admit, this one caught me off guard when I first started learning Python many years ago. I remember when I was first introduced to the wonders of default arguments when working with functions, I was using it mindlessly like nobody’s business. Here’s an example:

def add_book(book_id, storage=[]):
    storage.append(book_id)
    return storage

my_book_list = add_book(1)
print(my_book_list)

my_other_book_list = add_book(2)
print(my_other_book_list)

# Expectation:
# [1]
# [2]

# Reality:
# [1]
# [1, 2] Huh, what? But they are different variables!
Enter fullscreen mode Exit fullscreen mode

When a function is defined, the default arguments (e.g. storage in the example above) are created (i.e. evaluated) only once.

As a list in Python is mutable, the list will be modified every time you call the add_book function. Here, the same list is used over and over again instead of creating a new list.

What you should do instead

# Do:
def add_book(book_id, storage=None):
    if storage is None:
        storage = []

    storage.append(book_id)
    return storage
Enter fullscreen mode Exit fullscreen mode

The same concept applies while working with other mutable objects, i.e. dictionary in Python.

3. Not Using List Comprehension

List comprehension is undoubted one of the most distinct features in Python. Unarguably, it helps to improve code readability and is often considered more elegant and “pythonic”.

Having that said, new Python developers may struggle to fully leverage this feature, or at least get used to using it at the beginning. Here’s an example:

# Don't:
def generate_fruit_basket(fruits):
    """Without list comprehension"""
    basket = []
    for fruit in fruits:
        if fruit != "grapes":
            basket.append(fruit)

# Do:
def generate_fruit_basket_lc(fruits):
    """With list comprehension"""
    return [fruit for fruit in fruits if fruit != "grapes"]
Enter fullscreen mode Exit fullscreen mode

If you are new to Python, take a breath and spend some time learning list comprehensions. It will help you immensely, I promise.

The same logic applies to using dictionary comprehensions in Python.

4. Abusing List Comprehension

We get it. List comprehensions are “pythonic”. However, I have seen way too many instances where the use of list comprehension is being abused in various forms.

Worse, some list comprehensions are written to the extent that the code is not even readable anymore, defeating the original purpose of using list comprehensions.

Here’s an example of what you should NOT do:

# Don't:
def get_book_price(inventory):
    return [(book, price) for book in inventory if book.startswith('A') for price in inventory[book] if price > 10]

# Perhaps slightly better...? But please don't.
def get_book_price(inventory):
    return [
        (book, price) for book in inventory
        if book.startswith('A')
        for price in inventory[book]
        if price > 10
    ]
Enter fullscreen mode Exit fullscreen mode

While list comprehensions are more compact and run faster, we should avoid writing long and complex list comprehensions (especially in a single line) to ensure code readability. Do not get bogged down with list comprehensions.

5. Using Bare Except

I was guilty of this. Very guilty. As a beginner, I did not appreciate how some codebases explicitly catch exceptions. Back then, I find them overly verbose and redundant. I guess I was just being lazy.

Here’s an example of what a bare except look like:

# Do NOT ever use bare exception:
while True:
    try:
        input_string = input("Input something")
        converted_input = int(input_string)
        print(converted_input)

    except: # Can't do Ctrl + C to exit!
        print("Not a number!")
Enter fullscreen mode Exit fullscreen mode

In short, we should never use bare except as it catches all exceptions, including some that don’t want, i.e. KeyboardInterrupt in this example where we want to exit our application.

On top of that, it makes debugging a nightmare as it can cause our application to fail without us knowing why. So, stop using exceptions like this.

6. Using == and is the wrong way

Does it really matter though? I have seen too many in wild Python code. In case you didn’t know, they are different.

  • The is operator checks if the two items reference the same object (i.e. present in the same memory location). A.k.a reference equality.
  • The == operator checks for value equality. Here’s a brief example of what I mean:
# NOTE: In Python everything is an object, including list
listA = [1, 2, 3]
listB = [1, 2, 3]

listA is listB # False because they are NOT the same actual object
listA == listB # True because they are equivalent
Enter fullscreen mode Exit fullscreen mode

Remember, listA and listB are different objects.

Here’s a summary of the Do’s and Don’ts of using is and == operator:

# Do:
if foo is None:
    ...

# Don't:
if foo == None:
    ...

# Do:
if not bar:
    ...

# Don't:
if bar == False:
    ...

# Do:
if baz:
    ...

# Don't:
if baz is True:
    ...

# Don't
if baz == True:
    ...
Enter fullscreen mode Exit fullscreen mode

7. Using import star (asterisk)

Many would consider this an act of convenience. But this is just plain lazy.

By using import *, you risk polluting the current namespace as it imports all the functions and classes from the library.

What does “polluting namespace” even mean? Well, in layman terms, it basically means that whatever functions (or classes) that you have imported with import * may clash with the functions defined by you.

Basically, you run the risk of overriding variables or functions and eventually it becomes incredibly hard to tell which function is from which.


Closing Thoughts

Python is a relatively easy language to get started with programming. It comes with many mechanisms and paradigms which hugely improve our productivity as a developer.

Having that said, one can easily pick up the habits of writing bad code in Python. Upon reflection, this post also serves as a reminder for me (and hopefully you) to stay away from these bad Python coding habits.

What to do next

There are too many don’ts in this article.

Here’s something you can do — start using tools like autopep8, flake8, and pylint, or even mypy to keep your code quality at the highest standards. They will make you a better developer by helping you to check your code against standards like PEP8 and more.

I hope you find the pointers in this article helpful and happy coding!


This article was originally published at jerrynsh.com

Top comments (4)

Collapse
 
steelwolf180 profile image
Max Ong Zong Bao • Edited

Tbh I don't really use list compensation because it suffers from being too clever but ignores readability and debugging.

Take for example this that you wrote a as good example.

def generate_fruit_basket_lc(fruits):
"""With list comprehension"""
return [fruit for fruit in fruits if fruit != "grapes"

What if you were to encounter a bug in this code.

How would you debug it? Would you spend more time figuring out what went wrong by looking at the logic or debug it step by step?

But because you put it as one liner it become difficult to understand or debug in a step by step process.

Collapse
 
romansorin profile image
Roman Sorin

List comprehension is one of my favorite tools for writing effective statements and fewer lines, but it needs to be done right. The example is likely slightly exaggerted (the
"fruit for fruit in fruits" bit), but I've seen it done. I believe they are more readable and easier to understand if the names of variables/elements are chosen properly — but we know that naming is the hardest thing :)

Collapse
 
jerrynsh profile image
Jerry Ng • Edited

A long and complex list comprehension definitely suffers from being too "smart" and thus ignores readability.

However, when it comes to a short for loop, i.e. generating a list of numbers from a range, no one should be using a regular for loop with append .

# This is way more readable
nums = [i for i in range(10)]

# This is less readable, IMO we shouldn't have to do this
nums = []

for i in range(10):
    nums.append(i)
Enter fullscreen mode Exit fullscreen mode

Throwing in a single conditional if (i.e. the grapes example in the post) is fine IMHO. It really depends on what you and your team agree on at the end of the day. Nothing beats a good consistency in a shared codebase.

Collapse
 
jerrynsh profile image
Jerry Ng

With respect to list comprehensions, it also might be important to note that they are faster than standard for loops in most cases

Definitely a good mention haha