Python conventions - ISISComputingGroup/ibex_developers_manual GitHub Wiki

Wiki > genie_python > Python Conventions

For dependency conventions see Python dependencies.

Coding style

We try to follow PEP8 for coding style where possible or suitable.

A clear exception is when it comes to line length; PEP8 suggests a line length limit of 79 characters but defaults to 100, but PyCharm defaults to 120. I recommend following PEP8 and modifying PyCharm default to 100 on this (See: File -> Settings -> Editor -> Code Style -> Python -> Wrapping and Braces -> Hard wrap at: ).

PyCharm warns on many deviations from PEP8, please don't ignore it.

You can add a GitHub workflow to perform lint checking on your repository to enforce PEP8 standards as part of code reviews. The gitHub workflow can be found here: Pylint GitHub Workflow

Key points relating to general code formatting:

  • Use 4 spaces per indentation level
  • Spaces are the preferred indentation method not tabs
  • Surround top-level function and class definitions with two blank lines
  • Method definitions inside a class are surrounded by a single blank line
  • Class names should normally use the CapWords convention
  • Function/method names should be lowercase, with words separated by underscores as necessary to improve readability
  • Comments should have a space after the # and start with a capital letter (unless it is a variable name)
  • Files should have one blank line at the end
import os


class CapsWordName(object):
    def method_that_has_underscores(self, x, y=None):
        """
        See docstring guide below.
        """
        if y is not None:
            x += y
        return x

    def one_blank_line_before_next_method(self, max):
        """
        See docstring guide below.
        """
        # This comment starts with a capital letter
        total = 0        

        for i in range(max):
            if i % 2 == 0:
                # i is even. 
                # Previous line starts with lowercase because i is a variable name.
                total += i

        return total

    def _this_method_is_non_public(self):
        """
        The single underscore warns people using this class that this method 
        is not part of the API, so may change and should be used with care.

        It doesn't say you should not use it though.
        Python does not really have the concept of private.
        """
        return 123456       

Docstring format

Here we also deviate from PEP8 in that our format is based mostly on the Google style. See below for usage examples, but some key points to follow are:

  • Docstrings start and end with triple double-quotes
  • Newline after opening """
  • First line of text should be a one sentence description followed by a full-stop and a new line. More details can then follow
  • Args can have their suggested type declared in brackets. This is optional, but preferred in code a user may encounter, for example: in genie_python

Google style suggests that argument descriptions start with a capital and end with a full-stop.

    Args:
        state (string): The state to wait for (e.g. "paused").

We have decided to follow Google on this; however, some older docstrings may not match this, so please adjust them as you come across them.

PyCharm can be set to auto-generate Google style dostrings via File --> Settings --> Tools --> Python Integrated Tools

PyCharm also supports the ability to specify types in the docstring. This includes being able to write objects class names, for example:

    Returns:
           Group (block.Block): A block object containing a groups block information.

This is useful because it will syntax highlight potential type errors before you even debug, as well as allow you to quickly perform introspection on objects of that type in you code (Ctrl-Left Click).

Examples

class ExampleClass:
    """
    This is a class docstring.

    There should be a newline between this docstring and the following method.
    """

    def an_example_method(self, param1, param2=None, *args, **kwargs):
        """
        This is an example of a method.

        Here is where more details are written. It can take up multiple lines 
        and so on. blah blah blah.It can take up multiple lines and so on.
        blah blah blah.It can take up multiple lines and so on.
        blah blah blah.

        Note:
            self is not included in the 'Args' section.

        Args:
            param1 (int): The first parameter.
            param2 (string, optional): The second parameter. Defaults to None.
                Subsequent line(s) of description should be indented.
            *args: Variable length argument list.
            **kwargs: Arbitrary keyword arguments.

        Returns:
            bool: True if successful, False otherwise.

        Raises:
            ValueError: If param2 is equal to param1.
        """
def a_function_that_returns_none(param1):
    """
    This function does not return anything.

    Python returns None for functions that don't explicitly return anything. 
    As PyCharm automatically creates a return tag in the docstring we leave 
    it in and put None.
    
    Args:
        param1: Some input value.

    Returns: 
        None.
    """
def waitfor_runstate(state, maxwaitsecs=3600, onexit=False):
    """
    Wait for a particular instrument run state.

    Args:
        state (string): The state to wait for (e.g. "paused").
        maxwaitsecs (int, optional): The maximum time to wait before carrying on.
        onexit (bool, optional): Wait for runstate to change from the specified state.

    Examples:
        Wait for a run to enter the paused state:
        >>> waitfor_runstate("paused")

        Wait for a run to exit the paused state:
        >>> waitfor_runstate("paused", onexit=True)
    """
def get_number_periods():
    """
    Get the number of software periods.

    Returns:
        int: The number of periods.
    """
def get_blocknames(self):
    """ 
    Get all the blocknames including those in the components.

    Returns:
        list : The names of all the blocks.
    """

Code conventions

Use the Python 3 style print function

print('Hello, World')

AVOID using the Python 2 style print function.

Use format to construct strings from values

# Single value
print('Name: {}'.format('Eric'))

# Multiple values
print('Name: {}, Age: {}'.format('Eric', 21))

# With a class
print('Name: {user.name}, Age: {user.age}, Sex: {user.sex}'.format(user=usr))

# Limit to two decimal points
print('Name: {}, Age: {:.2f}'.format('Eric', 12.3456789))

AVOID using the older % formatter or concatenating strings with +.

Use os.path.join to create file paths from strings

os.path.join('directory', 'subdirectory1', 'subdirectory2')

AVOID combining strings with '\', '\\' or '/'

GitHub Workflows:

You can add a GitHub workflow to perform lint checking on your repository to enforce PEP8 standards here: Pylint GitHub Workflow