Style Guide
Please Log In for full access to the web site.
Note that this link will take you to an external site (https://shimmer.mit.edu) to authenticate, and then you will be redirected back to this page.
Table of contents
- Code documentation and comments
- Code spacing and readability
- Code redundancy
- For vs. while loops
- Checking conditions with if/else
- Classes
1) Code documentation and comments
Comments
Comments are essential for making code understandable and maintainable. They provide explanations and context that help both the original author and others who may read or modify the code in the future. This is helpful in 6.100 because you are able to quickly glance at your code and remember why/what you were doing. In addition, it is helpful when a TA/LA is helping you debug and for the checkoff as well!Getting the balance between too many comments and not enough comments is hard. The key heuristic is to comment your code in chunks to capture the main idea of a piece of code that is not-trivial. In other words, ask if someone else reading your code will think this is complicated or if its too simple to not need a comment. Various examples of good and bad commenting style are below.
Worst style: Commenting every line (Makes it harder to read and understand code)
def find_prime_numbers(limit):
"""
Find all prime numbers up to a given limit.
Parameters:
limit (int): The maximum number to check for primes
Return a list of prime numbers up to a limit
"""
# Create an empty list to store prime numbers
primes = [] # Primes will hold all prime numbers
# Start a loop to check each number from 2 to the limit
for num in range(2, limit): # Loop from 2 to the limit value
# Assume the number is prime until proven otherwise
is_prime = True # Set is_prime to True for each number
# Check if the current number is divisible by any smaller number
for divisor in range(2, int(num ** 0.5) + 1): # Check for divisors
# If the number is divisible, it's not prime
if num % divisor == 0: # Check if divisible by divisor
# Mark number as not prime
is_prime = False # Det is_prime to False
# Exit the loop because we've proven it's not prime
break # Exit loop
# If the number is still prime, add it to the list
if is_prime: # If the number is prime
primes.append(num) # Add the number to the primes list
# Return the list of prime numbers
return primes
Bad style: Over-explaining, repetitive, some not-helpful comments
def find_prime_numbers(limit):
"""
Find all prime numbers up to a given limit.
Parameters:
limit (int): The maximum number to check for primes
Return a list of prime numbers up to a limit
"""
primes = [] # List to store prime numbers
# Loop through each number from 2 up to the limit (exclusive)
for num in range(2, limit):
# We will check if "num" is a prime number by assuming it's prime
is_prime = True
# Loop through possible divisors starting from 2 up to the square root of "num"
for divisor in range(2, int(num ** 0.5) + 1):
# If "num" is divisible by "divisor", it's not prime
if num % divisor == 0:
is_prime = False
break # Break the loop since it's no longer prime
# If "is_prime" is still True, "num" is a prime number
if is_prime:
primes.append(num) # Add prime number to the list
# Return the list of prime numbers found
return primes
Good style: Concise, focused comments
def find_prime_numbers(limit):
"""
Find all prime numbers up to a given limit.
Parameters:
limit (int): The maximum number to check for primes
Return a list of prime numbers up to a limit
"""
primes = []
for num in range(2, limit):
# We will check if "num" is a prime number by assuming it's prime
is_prime = True
# Loop through possible divisors starting from 2 up to the square root of "num"
for divisor in range(2, int(num ** 0.5) + 1):
# If "num" is divisible by "divisor", it's not prime
if num % divisor == 0:
is_prime = False
# Break the loop since it's no longer prime
break
if is_prime:
primes.append(num)
return primes
Docstrings
When writing new classes and functions, it is important to document your intent by using docstrings. In particular, when there are a lot of new classes within a program, adding a docstring explaining the purpose of each class is a good idea.Even something as simple as:
class CiphertextMessage(Message):
"""
Subclass of Message that represents a shifted Message
"""
Including a docstring means the specification you’ve written can be accessed by those who try to create an instance of your class. For example, if you change your CiphertextMessage class definition to the above, run the file, then type the following at the interpreter:
>>> CiphertextMessage.__doc__
You will see your docstring pop up.
Beyond 6.100, it is useful when other people will inevitably read/utilize your code to know what is happening, including for career, projects, and collaboration!
2) Code spacing and readability
When writing Python code, it is important to note that your code is readable and written cleanly.
Make sure to remove any extra code that is not needed when you are done implementing a program.
This includes any stray print
statements and debugging code.
This also includes statements like pass
or raise NotImplementedError
or TO-DO
.
Make sure to also not use unnecessary paranthesis in your code, such as after return (i.e. do not do return(output)
, but instead simply return output
).
Line lengths
It is important to not have an extremely long line of code, including having comments on one line, such that it is hard to read the line of code.Bad style: Long line and hard to read/understand with no useful comments
def find_sum_and_product(num1, num2, num3): return num1 + num2 + num3, num1 * num2 * num3 # Calculate sum and product
Good style: Split for readability with docstring
def find_sum_and_product(num1, num2, num3):
"""
Calculate the sum and product of three numbers.
Parameters:
num1 (int): First number
num2 (int): Second number
num3 (int): Third number
Return a tuple containing (sum, product) of the three numbers
"""
sum_of_nums = num1 + num2 + num3
product_of_nums = num1 * num2 * num3
return sum_of_nums, product_of_nums
Spacing
Utilize space advantageously to make your code easy to read and understand. There should be spaces around essentially all Python operators like+
, -
, *
, /
, etc.
There should also be spaces around =
and other related symbols.
Bad style: No spaces between operators
total=5+10-3*2/4
Good style: Spaces between operators
total = 5 + 10 - 3 * 2 / 4
Variable names
Make sure to name your variables in snakeForm with informative meaning. Avoid one letter variable names in almost all cases, except for looping variable for indices only.Bad style: Variable names do not tell useful information
x = 5
y = 10
z = x * y
Good style: Descriptive variable names
length = 5
width = 10
area = length * width
3) Code redundancy
Code redundancy means repeated code within a program, and this can look like duplicate code blocks or recomputation of values. It is good practice to avoid redundancy since they can lead to inefficiencies and potential errors.Duplicate code blocks
If you find yourself using the same block of code multiple times, stop to think about how you could abstract it. Good programmers code by the Don't Repeat Yourself (DRY) principle.Bad style: Duplicate code with same functionality
def print_square_of_two():
result = 2 * 2
print(f"The square of 2 is {result}")
def print_square_of_three():
result = 3 * 3
print(f"The square of 3 is {result}")
Good style: Refactored duplicate code into single function
def print_square(number):
result = number * number
print(f"The square of {number} is {result}")
Recomputation of values
Likewise to duplicate code blocks, if you find yourself recomputing the same values multiple times, create a new variable to hold the newly computed value.Bad style: Same type conversion multiple times
length_str = "10"
print(f"The area of a square with length {int(length_str)} is {int(length_str)**2})
Good style: Single type conversion
length_str = "10"
length = int(length_str)
print(f"The area of a square with length {length} is {length**2})
4) For vs. while loops
Choosing the right loop type makes your code more legible and can also help prevent bugs. Everything that can be written with a for loop can be written with a while loop, but while loops can solve some problems that for loops don't address easily. You should usually write for loops when possible.
In general, use for loops when you know the number of iterations you need to do - e.g., 500 trials, one operation per character in a string, or an action on every element in a list. If you can describe the problem you're trying to solve in terms of each or every element in an iterable object, aim for a for loop. Using a for loop when possible will decrease the risk of writing an infinite loop and will generally prevent you from running into errors with incrementing counter variables.
Example (print "hello" 500 times):
for _ in range(500): # _ often indicates the looping variable not being used within the loop
print("hello")
Example (add 1 to every element in a list):
my_list = [5, 2, 7, -4, 0]
for i in range(len(my_list)):
my_list[i] += 1
If you're instead iterating for a certain condition to be satisfied, you want to use a while loop. While loops are useful when you can define the number of iterations in terms of a boolean variable. If you are waiting for a user to enter an input correctly or are waiting for a randomly generated value to exceed a certain amount, you'll want to use a while loop. Problems that can be described using "until" should use while loops.
Example (loop until the user enters a positive number):
num = float(input("Enter a positive number: "))
while num <= 0.0:
num = float(input("Enter a POSITIVE number: "))
Example (loop until the randomly generated number is greater than 0.5):
import random
num = random.random()
while num <= 0.5:
num = random.random()
To improve the average-case performance of your code, you can sometimes exit out of loops as soon as you find your answer; you'll find many loops that are used to find True/False answers follow this pattern. For example, say you want to check whether any value in a list is great than 5:
my_list = [1,2,3,4,5,6,7,8]
greater_than_five = False
for elem in my_list:
if elem > 5:
greater_than_five = True
break
5) Checking conditions with if/else
Often, people have a tendency to be overly verbose when checking conditionals. Observe the following examples with boolean conditional:Bad style: Explicit boolean comparison
# Suppose my_function returns True
if my_function() == True: # Code reduces to the following: True == True
return True
else:
return False
Good style: Function call inside conditional
if my_function(): # my_function returns True or False
return True
else:
return False
There is an important point to note here. Since my_function() is going to be a boolean, and we’re effectively returning the value of that boolean, there’s no reason not to just return the boolean itself.
Better style: Returning function call
return my_function() # my_function returns True or False
This is nice and concise, but what if we want to return True if my_function returns False, and False when it returns True? There’s a Python keyword for that! So, imagine our code starts as:
Bad style: Explicit boolean comparison
if my_function() == True: # my_function returns True or False
return False
else:
return True
We can use not
to write this as:
Better style: Returning function call
return not my_function() # my_function returns True or False
The similar idea applies to comparisons against None
.
Instead of explicit equality checks, is
is preferred due to being less error prone.
Bad style: Explicit none comparison
if my_variable == None:
return True
else:
return False
Good style: Using is
if my_variable is None: # Checks for identity, not equivalence
return True
else:
return False
6) Classes
Class names
Make sure to name your classes in PascalCase with informative meaning. Methods and attribute names should remain in snakeForm.Calling class methods
When calling class methods on objects,obj.method_name(args)
is preferable over class_name.method_name(obj, args)
because we do not make any assumptions about which class method_name belongs to.
The Python interpreter will look for a method called method_name
from within obj
’s class first.
Only if it doesn’t find an implementation of method_name in that class will it look in parent classes.
Accessing class attributes
Avoid directly accessing a class attribute because this is more prone to errors. Instead use getter and setter functions to access and modify class attributes.Bad style: Directly accessing attributes
'''
Suppose we have a class called Rectangle with attribute width and height and an instance of the Rectangle class called rect
'''
area = rect.width * rect.height
rect.width = 10
Good style: Using getter and setter functions
area = rect.get_width() * rect.get_height()
rect.set_width(10)
Dunder methods
Avoid calling explicit dunder methods. Dunder methods are functions that begin and end with__
and are special functions that interact with Python's built-in functions.
Suppose we have the following class and instance:
class Rectangle():
def __init__(self, width, height):
self.width = width
self.height = height
def __str__(self):
return f"This rectangle has width {self.width} and height {self.height}"
rect = Rectangle(5, 10)
Bad style: Explicitly calling dunder methods
print(rect.__str__())
Good style: Using dunder method
print(rect)
Inheritance
Always reuse the functionality of the superclass instead of rewriting it. This goes back to the concept that we should avoid repeated code.Bad style: Redefining same code
class Square(Rectangle):
def __init__(self, width, height):
self.width = width
self.height = height
Good style: Using inheritance
class Square(Rectangle):
def __init__(self, width, height):
Rectangle.__init__(self, width, height)
Best style: Using super
class Square(Rectangle):
def __init__(self, width, height):
super().__init__(width, height) # More general code
Takeaway
Style is inherently subjective, but there are good styles and bad styles. Think of simplicity, readability, and how someone other than you would think of your code! :)
Whenever you are in doubt about styling for Python in general, there are more standard and detailed style guidelines called "Python Enhancement Proposals" or PEP that you are free to reference. At the time of this writing, PEP 8 is the latest style guide for Python. Feel free to browse around.