Python Coding Style Guide - WheatonCS/Lexos GitHub Wiki

This guide gives Lexos coders the correct coding conventions to use when adding to and editing the Python code for Lexos.

PEP8

This standard needs to be satisfied in order for your code to be merged into the master branch. (This standard is checked by flake8 with the pep8-naming plugin.)

PEP8 needs to be strictly followed, since it is the most comprehensive and widely accepted style for the Python community.

Here are some of the basic coding conventions from PEP8:

  • Naming:

    • Function (and method) names should be lowercase, with words separated by an underscore:

      def function_name()

    • Local variable names should be lowercase, with words separated by an underscore:

      local_variable = 1

    • Constants should be written in all capital letters using underscores to seperate words:

      EXAMPLE_CONSTANTS = "constant string"

    • Class names should normally follow the CapWords convention:

      class ExampleClass

  • Comments:

    • Each line of a block comment starts with a # and a single space. (Do not use """ or ''' for block comments, they are used for docstrings):

      # hello world
      # here is the correct way to write a block comment
      # still in my block comment
      # this is the last line of my block comment
    • Inline comments should be separated by at least two spaces from the statement. They should start with a # and a single space:

      x = x + 1  # this is the correct way to write and inline comment
  • Individual Lines:

    • The max line length should be 79 characters.
    • Want to see how to wrap your long lines? Click here!
    • Your editor's will guide you to indenting correctly when you insert a line break in the middle of a line

Variable Naming

This standard needs to be satisfied in order for your code to be merged into the master branch

Avoid single-letter naming when you can:

YES 👍:

for index, element in enumerate(my_list):
    pass

NO 👎:

for i, e in enumerate(l):
    pass

DO NOT use function or type names as a variable name:

YES 👍:

def example(input_list):
    length = len(input_list)

NO 👎:

def example(list):  # list is a type name
    len = len(list)   # len is a function name

Clearly label your function input:

YES 👍:

def sort(unsorted_list):
    pass
def enable_files(file_ids):
    pass

NO 👎:

def sort(l):
    pass
def enable_files(ids):
    pass

HTML naming standard:

  • The id attribute should use lowercase words with an underscore.
  • The class attribute should use lowercase words with a hyphen.

YES 👍:

<p id="example_id" class="example-class"/>

NO 👎:

<p id="exampleid" class="exampleClass"/>

Documentation

Follow the standard for PyDocStyle

This standard needs to be satisfied in order for your code to be merged into the master branch. (This standard is checked by flake8 with the flake8_docstrings plugin.)

Do not leave code in your comments

  • Code in the comments is confusing for the reader, because they cannot distinguish comments from unused code
  • If we need old code, we have version control (git) so we can always find the old code. Leaving old code the in comments is redundant
  • For experimental code, you can open an issue. If we decide your idea is really good, but want to save it to write later, we can always open a new page in Wiki

Comment syntax

Comment syntax follows the Sphinx-style docstrings (inherited from PyCharm as the standard development editor for Lexos). Follow the link and scroll to the end for instructions for implementing Sphinx-style docstrings in VS Code.

YES 👍:

def __init__(self, word_list, file_name):
    """Calculate all the information of the given file.

    :param word_list: a dictionary map word to word count representing the
                        word count of particular file
    :param file_name: the file name of that file
    """

NO 👎:

def __init__(self, word_list, file_name):
    """
    Calculate all the information of the given file.

    Args:
        word_list: a dictionary map word to word count representing the
                        word count of particular file
        file_name: the file name of that file
    
    Returns:
        None
    """

The formal syntax of the docstring should be as follows:

"""One line summary: starts with capital letter, ends with period; the next line needs to be empty.

Multiline description (optional).
:para para_name1: para description 1 (optional).
:para para_name2: para description 2 (optional).
:return: description of the variable returned (optional).
"""

or

"""One line doc string: starts with capital letter, ends with period."""

Keep Your Functions Small

Typically dividing your function into smaller functions will help you to modulize your application.

Keep your functions short

A good rule of thumb is to make sure your function is no longer than 30 lines. This will make keeping track of what you are doing much easier.

Keep your functions flat

A good rule of thumb is that you cannot have more than 12 spaces indenting a line (no more than a 3 indenting structures).

The more indentations your function has the less readable Python becomes. It will also be more difficult to keep your line under 79 characters.

YES 👍:

class ExampleClass:
    
    # create private helper function when your logic is too complicated
    function __do_something_for_elem__(element):  # first indenting structure
        if element.property is True: # second indenting structure
            do_lots_of_things()   # third indenting structure

    function do_something_for_list(input_list):  # first indenting structure
         for element in input_list:  # second indenting structure
             __do_something_for_elem__(element)  # third indenting structure

NO 👎:

class ExampleClass:

    function do_something_for_list(input_list):  # first indenting structure
         for element in input_list:  # second indenting structure
             if element.property is True: # third indenting structure
                 do_lots_of_things()  # forth indenting structure, NOOOOOO!

Use Keyword Parameters

Typically using keyword parameters will make your code easier to understand:

YES 👍:

generate_matrix(use_freq=True,
                token_type="by_gram",
                token_size=2)

NO 👎:

generate_matrix(True, "by_gram", 2)

There are some exceptions:

YES 👍:

sin_of_x = sin(x)

NO 👎:

sin_of_x = sin(value=x)

Make Sure Your Editor is Happy with Your Code

In other words, make sure to follow basic Python coding conventions. When you are done with your code, your editor's linter should not give you any errors or warnings. (No squiqqly lines will appear in your code.)

YES 👍:

if some_variable is True:
    other_variable = False
else:
    other_variable = True

print(other_variable)  # The linter is happy with your cod

NO 👎:

if some_variable is True:
    other_variable = False

print(other_variable)  # The linter will give you an warning here

Avoid Mutating Your Variable

As hard as it is to believe, in most cases in our code you really do not need to mutate your variable.

List comprehension helps you a great deal.

YES 👍:

def zip_and_shift_to_positive(x_coords, y_coords):
    """
    Current function zips two lists (Xs and Ys) to xy_coord_list
    and then shifts the whole graph to make all the coordinates positive
    """
    # zips list
    xy_coord_list = zip(x_coords, y_coords)

    # shifts the list
    x_shift = min(x_coords)
    y_shift = min(y_coords)
    positive_coord_list = [[x - x_shift, y - y_shift] for x, y in coord_list]
    
    return positive_coord_list

NO 👎:

def zip_and_shift_to_positive(x_coords, y_coords):
    x_shift = min(x_coords)
    y_shift = min(y_coords)
    
    # should use list comprehension
    positive_coord_list = []
    for index in range(len(x_coords)):
        # very dangerous, since list is pass by refrence, 
        # editing lists in functions can easily render unwanted result
        x_coords[i] -= x_shift 
        y_coords[i] -= y_shift

        positive_coord_list.append([x_coords[i], y_coords[i]])  # append is much slower than list comprehension
    
    return positive_coord_list

When you want to mutate your variable, you can probably give it a new variable name

YES 👍:

my_sorted_list = sorted(my_list)

NO 👎:

my_list = sorted(my_list)

When you give your variable a new name, you are giving the variable a more accurate description, which therefore makes the code more clear.

NEVER mutate the type of a variable

Python gives you the freedom to mutate the type of a variable (since Python is dynamically typed), but a majority of programmers still think static typing provides better security to the program.

If you mutate the type of the variable, it is not the same thing as before. Therefore, you should give the variable a new name.

YES 👍:

json_dict = {'hello': 'lala', 'tata': 'haha'}
json_str = json.dump(json_dict)

NO 👎:

json_dict = {'hello': 'lala', 'tata': 'haha'}
json_dict = json.dump(json_dict)

As you can see, when you do a json.dump on the dict, it becomes a string. Therefore a more accurate description of the variable is not json_dict, but json_str

⚠️ **GitHub.com Fallback** ⚠️