15 best practices every Python developer must know

Manu mathew
Techdev
Published in
6 min readMar 24, 2022

--

  1. Indentation should be four spaces and not tabs

This is a mistake very often made mostly by accident because it’s hard to differentiate in many text editors or IDEs. Every editor / IDE will have a setting on the top menu bar for indentation, where you can choose the option ‘Indent using 4 spaces’ or a similar option so that you can continue using the tab key. Many times this mistake will cause an issue only after reaching production or while merging pull requests in git.

2. Always ensure variable names are not reserved keywords.

Another common mistake is using reserved keywords such as inbuilt function names as variable names which could probably cause unexpected behavior and consume a lot of time in debugging. Ex: dict = {1: 2, ‘a’: 1}

3. Don’t assign lambda expression to a variable

This will not cause any behavioral problem but it kind of beats the purpose of lambda so it’s part of PEP8 coding standards.

For example, the below lambda function needs to be avoided and instead written as a normal function.

res = lambda x: x % 2 == 0

but why? Because lambda function is an anonymous function which means it should not have a name (an identifier). It is meant to be embedded in other expressions such as

list(filter(lambda x: (Counter(str) == Counter(x)), my_list))

4. Do not add mutable default arguments to functions

In python, default arguments are evaluated on function definition (only once) and not each time during the function call.

# bad practice def append_to(element, to=[]):
to.append(element)
return to
my_list = append_to("a")
print(my_list)
Output
["a"]
my_second_list = append_to("b")
print(my_second_list)
Output
["a", "b"]
# good practice def append_to(element, to=None):
if to is None:
to = []
to.append(element)
return to

5. Always have default return statement or ensure explicit return statements for all possible scenarios

Python returns a None object when a function returns without an explicit return statement. This could occur either cause no return statement was specified or no return defined scenarios were matched while not having a default return. If the code calling the function is expecting an integer data type as in the below code, it would throw an error.

def test_func(a):
if a < 10:
return a
a = test_func(11) * 2
TypeError: unsupported operand type(s) for *: ‘NoneType’ and ‘int’

Either a default statement returning the value of matching data type should be added or it should be handled in the function call statement.

def test_func(a):
if a < 10:
return a
return 0
Orval = test_func(11)
if val != None:
a = val * 2

6. Avoid bad-except-order

Generic except statements should come after all specific except statements

# Bad codetry:
5 / 0
except Exception as e:
print("Exception")
# unreachable code!
except ZeroDivisionError as e:
print("ZeroDivisionError")
# Right codetry:
5 / 0
except ZeroDivisionError as e:
print("ZeroDivisionError")
except Exception as e:
print("Exception")

7. Not accessing protected members from outside the class

As we all know, practically there are no private or protected variables but rather implemented using a double underscore prefix naming convention. So technically it’s possible to access these variables outside the class or the expected scope but something that needs to be avoided as a best practice.

Reasons

  • If the code was written by someone else, it must have been given a restricted scope for a reason.
  • If you are defining a protected member which you are planning to use outside the scope, it will not be required and will be a misuse of the concept.

8. Sufficient use of comments

This is typically a mistake across the developer community irrespective of the language. Some of the reasons why people miss to add/edit comments are:

  • the developer is assuming he/she is going to be the sole person working on it.
  • a change was made in the middle of a high-priority bug fix and updating the comment was the least of concern. In this case, I suggest coming back once the fix is done and updating the comment.
  • the logic seemed pretty straightforward from the developer's perspective.
  • There were no comments anywhere in the existing code, just following the tradition :D
  • A part of the code was updated or deleted but the comment explaining it was somewhere on top so missed

Whatever be the reason be, the developer/team should come up with solutions to ensure that comments are being properly added, edited, or deleted. Few solutions are:

  • Checking for comments during PR approval
  • Regular review of code by self, peers, or manager
  • Keeping check-list to ensure it before every commit or deployment

9. Iterating over the items instead of using the index

A mistake often made by people switching from other programming languages is looping over a list or any sequence using the index while python supports iteration directly on the elements in it.

a_list = [1, 2, 3] # bad practice 
for index in range(len(a_list)):
item = a_list[index]
call_func(item)
# good practice
for item in a_list:
call_func(fruit)

10. Passing instance/class reference as the first argument to a method

Python does not pass references to an instance or class objects internally by itself. So if any class members — other methods or variables need to be accessed within the method, reference to the class or instance need to be passed explicitly in the code

class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
self.area = width * height
# missing first argument "self"
def area():
# self is undefined here
return self.area

11. Add a break statement when having anelsewithin a loop

def check_for_even_num(ip_list):
for item in ip_list:
if item % 2 == 0:
print("This list contains an even number.")
else:
print("This list has no even numbers.")
check_for_even_num(range(5)

Wrong Output

This list contains an even number.
This list has no even numbers.

Correct code

def check_for_even_num(ip_list):
for item in ip_list:
if item % 2 == 0:
print("This list contains an even number.")
# added break statement here
return

print("This list has no even numbers.")
check_for_even_num(range(5))Output
This list contains an even number.

12. Efficient Single line unpacking of elements

# bad practiceitems = [1, 2, 3]

item1 = items[0]
item2 = items[1]
item3 = items[2]
# good practiceitems = [4, 7, 18]
item1, item2, item3 = items

In the first code, Python has to iterate over the list for each item while it will be done in a single iteration in the second code. This will be very effective especially when the sequence being unpacked is large.

13. Avoid returning different types in a single function

This is similar to what I mentioned above in adding a default statement, to have the return type of default statement to match with the expected type. In a function, it’s bad practice as well unwanted complexity in having multiple return types. If your code demands so, you might probably need to relook on how to rewrite or subdivide the function again. If not immediately, it is bound to cause some sort of bug or issue with debugging or testing in the future.

14. Use ‘get’ to retrieve values from a dictionary

# good practice
sample_dict = {"message": "Hello, World!"}
data = sample_dict.get("message", "")
print(data)
Hello, World!# bad practice 1print(sample_dict["message"]) # throws error if key not present# bad practice 2
if "message" in sample_dict.keys()
print(sample_dict["message"]) # two dict loops, not efficient

15. Use collections.defaultdict when creating a dictionary whose elements need to be initialized with a default value if the key is not already present.

Consider the below example for a better understanding. There is a list ‘s’ with a list of tuples with color as the first element a car company as the second element. If we need to create a dict ‘d’ with color as key and the list of car companies with that color and sort it, we can use the defaultdict as in the below code and then sort it.

s = [('yellow', 'lamborghini'), ('blue', 'aston martin'), ('yellow', porsche), ('blue', 'BMW'), ('red', 'Ferrari')]
>>> d = defaultdict(list)
>>> for k, v in s:
... d[k].append(v)
...
>>> sorted(d.items())
[('blue', ['aston martin', 'BMW']), ('red', ['Ferrari']), ('yellow', ['lamborghini', 'porsche'])]

What are other best practices you know about? Let me know in the comments !!

If you are looking out for a job change, do read my article on virtual interview preparation.

--

--

Manu mathew
Techdev

Software engineer in Silicon valley of India - Bangalore/Bengaluru