Style Guide

You are not logged in.

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

  1. Code documentation and comments
  2. Code spacing and readability
  3. Code redundancy
  4. For vs. while loops
  5. Checking conditions with if/else
  6. 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.