DEV Community

Dollar Dhingra
Dollar Dhingra

Posted on • Edited on • Originally published at dollardhingra.com

Best Practices For Writing Clean Pythonic Code

Introduction

This article is a collection of best practices for more idiomatic python code especially if you are new to python.

Contribute

Feel free to contribute to this list, and I will thank you by including a link to your profile with the snippet!

1. Catching Exceptions

This is a sure-shot way to get your code into trouble

# very bad practice
try:
    do_something()
except:
    pass

# A slightly better way(included logging), but still a bad practice:
try:
    do_something()
except Exception:
    logging.exception() # although, logging is a good practice

# When we don't use `Exception` we will also be catching events like system exit. 
# So, using Exception means that we are only catching Exceptions.

# A better way:
try:
    do_something()
except ValueError:
    logging.exception()
    # some code to handle your exception gracefully if required

Enter fullscreen mode Exit fullscreen mode

Here we have used a specific type of Exception i.e. ValueError.

2. Name Casing


# using camelCase is not a convention in python
def isEmpty(sampleArr):
    ...

# instead use snake_case in python
def is_empty(sample_arr):
    ...
Enter fullscreen mode Exit fullscreen mode

In python, snake_case is preferred for variables, functions and method names. However, for classes, PascalCase is used.

3. Chained comparison operators

There are multiple ways of comparing in Python:

# Don't do this:
if 0 < x and x < 10:
    print('x is greater than 0 but less than 10')


# Instead, do this:
if 0 < x < 10:
    print('x is greater than 0 but less than 10')
Enter fullscreen mode Exit fullscreen mode

4. Mutable default arguments


# wrong way!
# a sure shot way to get some unintended bugs in your code
def add_fruit(fruit, box=[]):
    box.append(fruit)
    return box


# correct way!
# recommended way for handling mutable default arguments:
def add_fruit(fruit, box=None):
    if box is None:
        box = []

    box.append(fruit)
    return box
Enter fullscreen mode Exit fullscreen mode

Read more about mutable default arguments here

5. String Formatting


# Avoid using it
# %-formatting
name = "James Bond"
profession = "Secret Agent"
print("Hello, %s. You are a %s." % (name, profession))

# slightly better
# str.format()
print("Hello, {}. You are a {}.".format(name, profession))

# Short, crisp and faster!
# f-strings
print(f"Hello, {name}. You are a {profession}.")
Enter fullscreen mode Exit fullscreen mode

The f in f-strings may as well stand for "fast." f-strings are faster than both %-formatting and str.format(). (Source)

6. Top-level script environment

Executes only if it is run as a script and not as a module

# Filename: run.py

if __name__ == '__main__':
    print('Hello from script!')
Enter fullscreen mode Exit fullscreen mode
$ python run.py
$ Hello from script!
Enter fullscreen mode Exit fullscreen mode

Hello from script! will not be printed if the module is imported into any other module.

7. Conditional expressions

if x < 10:
    return 1
else:
    return 2
Enter fullscreen mode Exit fullscreen mode

Can be reduced to this one:

return 1 if x < 10 else 2
Enter fullscreen mode Exit fullscreen mode

8. Iterating over an iterator

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.

list_of_fruits = ["apple", "pear", "orange"]

# bad practice
for i in range(len(list_of_fruits)):
    fruit = list_of_fruits[i]
    process_fruit(fruit)

# good practice
for fruit in list_of_fruits:
    process_fruit(fruit)
Enter fullscreen mode Exit fullscreen mode

9. Indexing/Counting during iteration

# Don't do this:
index = 0
for value in collection:
    print(index, value)
    index += 1


# Nor this:
for index in range(len(collection)):
    value = collection[index]
    print(index, value)

# Definitely don't do this:
index = 0
while index < len(collection):
    value = collection[index]
    print(index, value)
    index += 1

# Instead, use `enumerate()`
for index, value in enumerate(collection):
    print(index, value)
Enter fullscreen mode Exit fullscreen mode

10. Using context managers

Python provides context managers that manage the overhead of initializing and clearing up the resources and let
you focus on the implementation. For example in the case of reading a file, you don't need to be concerned
to close the file manually.

d = {"foo": 1}

# bad practice 
f = open("./data.csv", "wb")
f.write("some data")

v = d["bar"] # KeyError
# f.close() never executes which leads to memory issues

f.close()

# good practice
with open("./data.csv", "wb") as f:
    f.write("some data")
    v = d["bar"]
# python still executes f.close() even if the KeyError exception occurs
Enter fullscreen mode Exit fullscreen mode

11. Using set for searching instead of a list

s = set(['s', 'p', 'a', 'm'])
l = ['s', 'p', 'a', 'm']

# ok for small no. of elements
def lookup_list(l):
return 's' in l # O(n)


# better for large no. of elements
def lookup_set(s):
return 's' in s # O(1)
Enter fullscreen mode Exit fullscreen mode

Sets are implemented using hash in python, which makes searching of element faster(O(1)) as compared to searching in a
list(O(n)).

12. using * while importing a module

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

# bad practice 
from math import *
x = ceil(x)

# good practice 
from math import ceil   
x = ceil(x) # we know where ceil comes from
Enter fullscreen mode Exit fullscreen mode

13. using items() for iterating a dictionary


d = {
     "name": "Aarya", 
     "age": 13
 }

# Dont do this
for key in d:
    print(f"{key} = {d[key]}")

# Instead do this
for key,val in d.items():
    print(f"{key} = {val}")
Enter fullscreen mode Exit fullscreen mode

Sources

More Articles You May Like

Top comments (10)

Collapse
 
md2perpe profile image
Per Persson

F-strings are good, but sometimes you need to use the older ones. For example, I produced string templates where some parts could be filled directly, but other parts should be filled later. Then I used strings like f"movie/{viewing_direction}/frames/%03d.png". Here, viewing_direction could be filled directly, but the %03d part would be filled at another place.

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
md2perpe profile image
Per Persson

Sure, but it is hardly easier nor better.

Collapse
 
andrewbaisden profile image
Andrew Baisden

Python is such a clean and easy language to learn.

Collapse
 
lweiner profile image
Lukas Weiner

You got some really useful points there. On 7. you could just remove the else statement and immediately return. It's my preferred way to return because I don't like the conditional returning. But that's just my personal preference.

Collapse
 
dollardhingra profile image
Dollar Dhingra

Yes, you are right. We can remove return. I just wanted to show the syntax where we can include return

Collapse
 
mccurcio profile image
Matt Curcio

Really good points,
Thanks

Collapse
 
dollardhingra profile image
Dollar Dhingra

Thanks!

Collapse
 
hieuminh67 profile image
HieuMinh67

In 4. You can make it “if not box:”

Collapse
 
dollardhingra profile image
Dollar Dhingra

if not box condition will also be True if box value is 0 or ""(blank string). is None is exclusively used to check for comparison to None.