Coding Style Guide

This guide summarizes how we MONK developers think the source code should be formatted. Consider these guidelines advice rather than law. Sometimes you might hit a point, where the current version of the document can not help you. In this case you can do the following:

  1. Check whether the newest version of this document contains any changes concerning your situation. Also consider building the docs yourself.
  2. Refer to PEP-008, the official style guide for Python core development. The Python Core project is, of course, a lot bigger than MONK, meaning you might find something there, that has not been addressed in MONK yet. Place a comment in the code explaining why you were not able to follow MONK’s style guide. If possible, create an issue and try to get the style guide changed accordingly.
  3. Discuss with other developers whether there might be a way that you are not aware of.
  4. Do what makes most sense in your context. Use comments extensively and find meaningful ways to tell pylint and other tools that this line is to be treated as an exception.

In General

  • Avoid features specific to your Python binary. That includes your language version as well as the interpreter you are using, e.g., CPython, Jython, or Pypy.

  • Every line should consist of 79 characters at most.

  • No line should contain more than one statement.

  • All classes should be New Style Classes.

  • Do not use getters/setters. Use properties instead.

  • When comparing singletons like None always use is instead of ==.

  • When overwriting comparator functions like __eq__, ensure that you overwrite all of them.

  • Every function consists of 30 statements and 12 branches at most.

  • Use the with clause to open context managers, e.g. when you use open().

  • Write your code as type independently as possible, since we should reap the benefits of dynamic typing while using it. E.g. use “duck testing”.

  • Use isinstance() instead of type() comparisons because it also checks for inheritance.

  • When checking for emptyness of lists, consider that empty lists are already False. There is no need to check for their length.

  • Do not check for the True-ness of a value explicitely with if bool_val is True: because it is already True or False. Use if bool_val: instead.

  • Callable Python scripts should always contain a main() method. This way your code will not only be executable by your script but can also be used by other Python modules which import yours. If you do not include one, and instead put your code directly into the if, then only your script can make use of that code, which is not preferred. Example:

    def main():
        print("hello world")
    
    if __name__ == '__main__':
        main()
    

Whitespace

In Python whitespace has meaning. Thus, treating whitespace should receive even more consideration than in other languages. Whitespace means:

  • (a space character)
  • \t (a tab character)
  • \n (a line feed character; typical for most Unix-like operating systems)
  • \r\n (a carriage return character, followed by a line feed character; typical for Windows)

Rules:

  • 4 spaces indentation
  • no tabulator characters, only spaces
  • New lines are \n characters.
  • Classes, functions, and blocks should be separated by 2 empty lines each.
  • no whitespace in params, brackets, and braces
  • no whitespace before commas, semicolons, and colons
  • no whitespace before ( in function and class headers
  • no whitespace before list indexing, e.g., list[5], slice[3:4]
  • no whitespace in default param assignments, e.g., def function(a=13):
  • exactly one space around assignments/operators

A complete example of correct whitespacing:

def function(a, b=5):
    my_list[b] = a
    print(my_list[2:3])


def another_function(d, e):
    f = d + e
    function(a=f, b=e)

Naming

Files, folders, modules, classes, functions, and attributes are to be named in the following fashion:

  • files, folders, modules, packages: all_lower_case
  • variables (including global variables): all_lower_case
  • constants (convention only, not enforced by interpreter): ALL_UPPER_CASE
  • classes: BigCamelCase
  • functions, methods: all_lower_case
  • Naming something with a leading underscore declares it to be soft private (e.g. _like_javas_protected).
  • Naming something with two leading underscores makes it private, i.e. only usable inside this class (e.g. __only_for_me).
  • Use of double underscores at beginning and end should be avoided, since Python itself uses this convention for special names (e.g. something.__doc__)
  • Use self to reference the object calling a method.
  • Use cls to reference the class in class functions.

Files - Modules

  • Files are to be encoded using utf-8.

  • Every file starts with the following 2 lines:

    #!/usr/bin/env python
    # -*- encoding: utf-8 -*-
    

    The first line calls the python binary the way you would from the shell. This way it is possible to use the script in a virtual environment. The second line tells other tools about the expected encoding of this file.

  • Following these there is an empty line followed by the copyright and licensing text.

  • After this general information there is the module docstring.

  • The docstring is followed by imports in this order:
    • general imports from the Python standard library
    • imports from frameworks
    • imports from your own project
  • Next are global variables and constants, if necessary (use of them is generally discouraged).

  • Next, there is the main part containing the class.

  • If required, the main() function follows below the class.

  • At the end there is the call of the main() function:

    if __name__ == '__main__':
        main()
    

Comments

In general it is best to write code as self-explanatory as possible. Yet sometimes you cannot get around writing comments to make things clear. Here are some situations in which you should write a comment:

  • Each module, class, and function needs to be accompanied by a docstring.
  • Whenever you find yourself writing code that cannot be understood without explanation (although you might want to consider refactoring the code instead).
  • If you want pylint or other static code checkers to ignore a piece of code that violates the existing coding style.
  • If you took a sizable piece of code from a book or a website, reference the source (and be sure to check the license of that code).
  • Reference dependencies between code parts, which might not be obvious. This is crucial, if someone wants to make changes on either side, as these might introduce new bugs caused by unknown dependencies.

Comments themselves must also be written uniformly. Therefore you should follow these requirements when writing a comment:

  • Each comment is written in English.

  • Each comment describes something underneath, excepting docstrings which also partly describe code that is above them, e.g. in function doc strings.

  • To put it the other way around: comments are not appended to a line of code, instead they are written above the line they describe:

    #good comment about do_something
    do_something(1,2,3) #bad comment about do_something
    #bad comment about do_something
    
  • Exceptions to this are docstrings which usually are directly underneath of what is to be described:

    class Something(object):
        """This is a docstring.
    
        It is put directly underneath the class definition.
        """
    
    
        def __init__(self):
            """This is also a docstring.
    
            It is put directly underneath the method definition.
            """
    
            #This is not a docstring, thus it is put above its target.
            do_something(4,5,6)
    
  • Each comment is indented the same way as the text underneath, not as the text above it. This is because indented text is treated as inside a function, while the next line of the same indention is treated as outside the definition, as with every other Python code:

    def do_something(*args):
        """This is the correct way to indent a docstring.
    
        The Python compiler understands that this belongs to the function
        declaration above.
        """
    
    def do_else():
    """This is the wrong way to indent a docstring.
    
    It won't even be recognised as a docstring.
    """
    
  • Texts in comments are parsable by the Sphinx documentation generator. This can be a complicated issue, so if you encounter any problems, do not expect to find a 1 minute solution here! Seriously, start reading the Sphinx website, if you need to write more than one or two lines of documentation!

  • Single line comments begin with a hash (#) character.

  • Multiline comments, including docstring, adhere to the following rules:
    • They start with three straight double quote (""") characters.
    • The first line of the comment starts directly afterwards and is treated as a short summary by many tools.
    • Newlines are used meaningfully inside and there should be at least one empty line between the summary (the first line) and the verbose documentation (the rest of the docstring).
    • The commenting text is not followed by an empty line.
    • The end is marked by a line consisting exclusively of three straight double quote (""") characters.
    • They should consist of complete sentences.
    • They should contain descriptions for elements like parameters, as described in the Sphinx Docs.
  • Comments for version control systems should be added to the __version__ magic variable.

Exception Handling

In most cases exception handling should be done like in any other proper Python project. Here are some things to consider:

  • Use Exception objects and raise to initiate exception handling.

  • State Exception classes explicitly in except clauses because explicit is better than implicit.

  • Minimize the number of lines in try clauses to avoid Pokemon exception handling.

  • In Python, it is encouraged to use function parameters as expected instead of forcing certain types. If the delivered objects do not have the expected capabilities and there is no sensible way to handle this, then raise exceptions:

    def u_gonna_quack(duck):
        """I'm expecting a duck but don't force it to be one.
        """
        try:
            duck.quack()
        except AttributeError:
            print("The param duck can't quack.")
    

Imports

As stated above, import calls should be written below the module docstring, above the constant/global variable definitions, and in the following order:

  • general imports from the Python standard library
  • imports from frameworks
  • imports from your own project

Different imports should be on different lines. An exception to this are statements like from abc import x,y,z, making different imports from the same source. This is discouraged for other reasons, though. Instead of from abc import x,y,z you should use import abc and then refer to abc.x later on. This way it is easier to identify where something comes from, even though it requires a little more work typing. Always remember explicit is better than implicit.

The End

You’ve read it all. I’m so proud of you!

Table Of Contents

This Page