PEP 8 Is Bug Repellent for Your Python Code
In all the years that developers have been writing Python code, there must have been millions of bugs written, detected, and resolved. I’m sure you’ll have encountered some of your own too.
And along the way, developers have realized that certain patterns reduce the likelihood of encountering bugs, leaving you free to work on the exciting stuff! 🎉
Some of these are the design patterns and SOLID design principles that we’ll be covering later in the course, but PEP 8 also has some programming guidelines of its own that help you write bug-repellent code.
In this chapter, we’ll look at some of the most important cases. Let’s get started!
Consistent Return Types
Here’s a badly-designed division function. Can you see the four different types of value it can return?
def confusing_division(top, bottom):
if bottom == 0:
return False
if top != 0:
if top % bottom == 0:
return top/bottom
return "Does not divide exactly!"
Depending on the input, it might return:
A Boolean.
A number (float).
A string.
None (if
top
is equal to 0), and no return statement is encountered.
This makes it very awkward to write code that handles it. PEP 8 recommends that:
Either all return statements should return a value, or none of them should.
All return types should be the same (unless there’s a very good reason!).
Use
return None
rather than a barereturn
.
The division function could better be written:
def less_confusing_division(top, bottom):
if bottom == 0:
return None
if top != 0:
if top % bottom == 0:
return top/bottom
return None
return None
Now the handling code knows that it will either receive a number or None, and you can write:
if less_confusing_division(7, 3) is not None:
# do something
else:
# so something else
If it’s important to return a specific error message (e.g., “Does not divide exactly!”), then it’s better to throw and catch an exception.
Basic String Matching
Here’s an example of some neat Python syntax that can get you into trouble! What does the code below do?
phone_number = "01234567890"
if phone_number[:3] == "012":
print("Yes!")
First, I took a slice of the first three characters of the phone number and checked whether that was equal to “012.” In other words, I checked whether it starts with “012.”
But what happens if you want to change this to check whether the phone number starts with “01234”? You have to remember to change the length of the list slice from 3 to 5:
phone_number = "01234567890"
if phone_number[:5] == "01234":
print("Yes!")
An easy detail to understand, but also easy to overlook when modifying your code! Especially if the comparison string is elsewhere:
phone_number = "01234567890"
if phone_number[:5] == DIALING_CODE_FOR_BEDFORD:
print("Yes!")
Unless you find out the dialing code (it’s “01234”), you can’t tell if this is right. 😐
So what can we do instead?
Python is great at giving you easy ways to solve common problems, and here is the str.startswith
function, which does what you’d hope:
if phone_number.startswith(DIALING_CODE_FOR_BEDFORD):
print("Of course!")
This is a much more maintainable way of writing this code, and it’s even explicitly recommended by PEP 8. Here are some related best practices when comparing strings in Python:
phone_number = "01234567890"
if phone_number.endswith("7890"):
print("Verily!")
if "345" in phone_number:
print("Affirmative!")
Trying Too Hard With Exceptions
You don’t want your code to have errors - and one easy way to prevent them is by using try-except blocks.
The code below repeatedly asks the user for an index and then prints out the relevant element from the sequence. But we need to check that the input is valid!
How would you complete this code so that the program prints a relevant error message before asking again for an index if an exception is raised? Take a few moments to plan out an answer, and then watch the video below.
sequence = ["a", "b", "c", "d", "e"]
while True:
try:
index = int(input("Which index shall we look for? "))
print("That was element", sequence[index])
So what did you learn?
The try-block should be over the minimum code possible to avoid masking other bugs.
Never use a bare except-clause as this interferes with your ability to shut down the program with
KeyboardInterrupt
andSystemExit
exceptions. 😱
PEP 8 Programming Recommendations
There are way too many programming guidelines in PEP 8 to cover them all in this course.
In a way, it’s like a washing machine manual - it’s very long with lots of instructions that you will seldom need. But it’s important to know the basics (how to add detergent, use the common settings, and turn the machine on/off), and it’s useful to have handy when you need to refer to it!
Let’s Recap!
PEP 8 includes various programming recommendations to help you write bug-repellent code.
Functions should return only one type (int, string, Boolean, etc.) and/or None.
str.startswith
andstr.endswith
are preferred to slicing strings since you only need to change one element of your code when modifying them.Try-blocks should be placed over the minimum code possible.
Except-clauses should always catch
Exception
(or a specific type of exception).
Now that you’ve finished a tour of PEP 8, let’s check out some automated tools that can help you write good, clean, PEP 8-compliant code!