Skip to content

Coding Style Guide

Vasileios Karakasis edited this page Mar 26, 2020 · 12 revisions

ReFrame follows the PEP8 coding style with some exceptions. These exceptions are mostly based on our perception of some aesthetic aspects of the code.

In short, we ignore the following PEP8 error codes:

  • E129: visually indented line with same indent as next logical line
  • E221: multiple spaces before operator
  • E226: missing whitespace around arithmetic operator
  • E241: whitespace after :
  • E272: multiple spaces before keyword
  • E741: do not use variables named 'l', 'O', or 'I
  • E742: do not define classes named ‘l’, ‘O’, or ‘I’
  • E743: do not define functions named ‘l’, ‘O’, or ‘I’
  • W504: line break after binary operator

A rationale for these exceptions follows along with some other formatting conventions that are not prescribed by PEP8 explicitly, but should be followed by the ReFrame code.

Variable names

Variable names must be descriptive but not excessively long. We prefer abbreviations. We do allow length one variables of any character in certain contexts, e.g., as automatic variables in unit tests:

# OK
l = [1, 2]

# not OK
alist = [1, 2]

We believe that the appropriate variable naming is a matter of proper code review. For this reason, we choose to ignore the errors E741, E742 and E743.

Line continuation

We prefer the Pythonic implicit line continuation inside parentheses or brackets. This happens naturally for lists, tuples, sets and dictionaries, but for expressions or lengthy assingments you must use parentheses explicitly instead of the \ line continuation character:

# OK
if (a_long_var > 0 and
    b_long_var == 3):
    do_sth()

# not OK
if a_long_var > 0 and \
   b_long_var == 3:
    do_sth()

However, the above if statement might give the E129 formatting error, but we choose to ignore it, because we prefer to be consistent with our line continuation policy. After all, PEP8 leaves it free.

NOTE on commenting if blocks: A comment that explains a specific branch should go inside the branch, not before the if. If the comment explains generally what we check, it should go outside.

Use of blank lines

Apart from the PEP8 requirements of the number of blank lines between functions, methods and classes, ReFrame code should follow the following rules:

  • Leave a blank line after each indented block of code, e.g., after if, for, while, with and try/except statements.
  • Leave a blank line before a line comment that explains the code following it
  • Other blank lines are discouraged.

Here are some examples:

if msg == 'hello':
    do_sth()
else:
    do_sth_else()

do_more_stuff_now()

# not OK
if msg == 'hello':
    do_sth()
else:
    do_sth_else()
do_more_stuff_now()

# not OK
if msg == 'hello':
    do_sth()

else:
    do_sth_else()

do_more_stuff_now()

# not OK
if msg == 'hello':
    do_sth()
else:

    do_sth_else()

do_more_stuff_now()

# OK
try:
    x = mydict[key]
except KeyError:
    handle_error()

do_other_stuff_now()

# not OK
try:
    x = mydict[key]

except KeyError:
    handle_error()

do_other_stuff_now()

# not OK
try:
    x = mydict[key]
except KeyError:

    handle_error()

do_other_stuff_now()

Some more examples follow on the use of single comments and blank lines.

# OK

# Create foos and bars
foo1 = Foo()
foo2 = Foo()
bar1 = Bar()
bar2 = Bar()

# Connect foos with bars
foo1.connect(bar1)
foo2.connect(bar2)


# not OK

# Create foos and bars
foo1 = Foo()
foo2 = Foo()
bar1 = Bar()
bar2 = Bar()
# Connect foos with bars
foo1.connect(bar1)
foo2.connect(bar2)


# maybe OK

# Create foos and bars
foo1 = Foo()
foo2 = Foo()
bar1 = Bar()
bar2 = Bar()

foo1.connect(bar1)
foo2.connect(bar2)

Formatting dictionaries

When formatting dictionaries, we follow the PEP8 convention in general, with the exception that we allow multiple spaces after : in order to align the values. This forces us to ignore E241.

# OK -- PEP8
multiline_dict = {
    'foo': 1,
    'foobar': 2
}

# OK
multiline_dict = {
    'foo':    1,
    'foobar': 2
}

# not OK
multiline_dict = {
    'foo'    : 1,
    'foobar' : 2
}

# not OK
multiline_dict = {
    'foo'   : 1,
    'foobar': 2
}

Formatting operators

In multiline conditionals we always split the line after the operator. For aesthetics reasons we allow alignment of the binary operators as follows:

# OK
if (a_long_var > 0  and
    b_long_var == 3 and
    smaller != 5):
    do_sth()

However, if the two lines have very different lengths, it's better not to align. Similar rules apply to assignments in successive lines:

# OK
a_long   = 1
a_longer = 2

# OK, but maybe ugly
a_long      = 1
a_very_long = 2

For these reasons, we ignore E221 and W504.

Finally, we allow the following type of aligning in successive assignemnts:

cflags   = my_cflags   or ''
cxxflags = my_cppflags or ''

The alignment of or here forces us to ignore E272 as well.

We are also a bit relaxed regarding the whitespace around arithmetic operators. We generally prefer the whitespace around every arithmetic operator, however, we accept removal of the whitespace in cases it makes the reading of an expression clearer (e.g., by creating visual groupings of the operands of operators with higher precedence):

# OK
n = 2*x + 1   # (1)

# OK, but not so readable as the previous one
n = 2 * x + 1 # (2)

# not OK
n = 2*x+1     # (3)

In order to accept syntax (1), we ignore also E226.

Formatting strings

We use single quotes for strings, except if a single quote is part of the string, in which case we are using double quotes:

# OK
s = 'hello, world'

# OK
s = "could not find file `foo.txt'"

# not OK
s = "hello, world"

For consistency, we use three single quotes for docstrings:

def foo():
    '''Returns the FOO number.'''

Imports

PEP8 leaves some grey area regarding the imports. Apart from their order (standard library, third-party, application), it does not go into detail on how to arrange the import ... vs. from ... import ... statements. To promote consistency, our coding style builds upon PEP8 and defines the following rules:

  • The imports must be grouped into three major groups as required by PEP8.
  • The different groups must be separated by a single blank line.
  • Inside each group, import ... statements must precede the from ... import ... statements.
  • All the import ... and the from ... import ... statements must be sorted alphabetically.
  • Names imported with single line import statements must be sorted alphabetically

An example of an import sequence follows:

import abc
import os
from datetime import datetime
from subprocess import Popen

import thirdparty
import thirdparty.core
from thirdparty.abc import sth
from thirdparty.module import sth_else

import reframe.utility.sanity as sn
from reframe.core.pipeline import RegressionTest
from reframe.core.exceptions import ConfigError, JobError

Formatting tools

  • pycodestyle
    • Usage: pycodestyle --ignore=E129,E221,E226,E241,E272,E741,E742,E743,W504 [FILE|DIR]
    • Produces a list non-compliances.
  • autopep8
    • Based on pycodestyle, auto-corrects the source file.
    • Usage: autopep8 -i --ignore=E129,E221,E226,E241,E272,E741,E742,E743,W504 [FILE]

NOTE: Although auto-formatting tools do a great job in homogenizing the code and correcting formatting mistakes, they cannot fully replace the developer's best judgement, especially when splitting long lines. Often they will split in not so meaningful places just to meet the formatting requirements. As a rule of thumb, try to conform to the style as you type.

Setting up your editor

  • Emacs:
    • Install the py-autopep8 package
    • Add the following lines in your .emacs file (format file on save):
      (add-hook 'python-mode-hook 'py-autopep8-enable-on-save)
      (setq py-autopep8-options '("--ignore=E129,E221,E226,E241,E272,E741,E742,E743,W504"))
  • Vim:

Copyright notice

ReFrame is released under the BSD-3 license. The following text should be inserted in the beginning of each file of the project:

# Copyright 2016-<current-year> Swiss National Supercomputing Centre (CSCS/ETH Zurich)
# ReFrame Project Developers. See the top-level LICENSE file for details.
#
# SPDX-License-Identifier: BSD-3-Clause