Skip to content

Code Style Guide

Huoran Li edited this page Jun 13, 2022 · 12 revisions

Extension Recommendation

VSCode

If your working IDE is VSCode, we recommend you to install and use these extensions:

  • Code Spell Checker: Check the spelling of your code and help you fix the typo in time.
  • EditorConfig for VS Code: Automatically fix formatting issues according to the .editorconfig file when saving.
  • Trailing Spaces: Automatically remove the unwanted trailing spaces when saving.

Auto-formatting Scripts

MARO uses pre-commit to manage the automatic format toolchain. The detailed configuration can be found in .pre-commit-config.yaml. You could run pre-commit install to install pre-commit so that pre-commit will run on every commit. You could also manually run pre-commit by executing:

pre-commit run --all-files

It is okay to run part of hooks by executing pre-commit run <hook_id>, but we strongly recommend running the entire workflow as the last step before you commit your changes.

In general, we involve autoflake, add-trailing-comma, black, isort, and flake8 in the pre-commit. To find the detailed configuration of each tool:

  • black and isort: .github/linters/pyproject.toml
  • flake8: .github/linters/tox.ini

We have an identical workflow to check codes on Github. Any pull request without passing all checks will be denied.

In-place changes V.S. check-only

Currently, add-trailing-comma, autoflake and black in pre-commit will change the file in-place (i.e., re-write the source files). Meanwhile, isort and flake8 will not change the files. Instead, they will run in "check-only" mode, i.e., only raise errors. You should fix these errors by yourselves.

For isort, if you wish to let it modify source files in-place, you could remove the --check in .pre-commit-config.yaml in the isort section.

Since pre-commit might make in-place changes, please ALWAYS check the git diff carefully before actually submitting your code to avoid unexpected errors.

Magic trailing comma in black

It is worthy to notice that Black will try to collapse multiple lines into one when possible. If you want to leave the code as what it is, you could use "magic trailing comma" to interact with Black. Please see Black's documentation for details. MARO uses add-trailing-comma to solve this issue, and that is why we will run add-trailing-comma and black two times.

Here is a simple example:

# The following code will be reformatted to `obj = func(param1, param2)` by Black.
obj = func(
    param1,
    param2
)

# The following code will not be reformatted by Black since there is a magic trailing comma at the end.
obj = func(
    param1,
    param2,
)

To utilize this feature, you can manually add commas for the code that you do not want Black to wrap. You can also run add-trailing-comma before running Black to solve this issue automatically.

Ignore special files

If there are files that should not be modified / checked by pre-commit, you could explicitly configure them in the configuration files. For example, files that should be ignored by Black could be specified in .github/linters/pyproject.toml. Files that should be ignored by isort could be specified in .github/linters/pyproject.toml or just add # isort: skip_file at the beginning of the files.

flake8 is a little special. In addition to configuring in .github/linters/tox.ini, you also need to add the ignored files in .pre-commit-config.yaml, since the exclude section in .github/linters/tox.ini will not be used by pre-commit.

Python Style

Generally speaking, we follow Google Python Style Guide, and the differences/highlights are the below points:

  • Line length: maximum line length is 120 characters.
  • Comments: never mix code and comment in one line.
  • Parameter list: use type declaration as much as you can.
  • Function call: use keyword arguments.
  • String format: use " if you can, use f-Strings.
  • Log: use maro.utils.logger.
  • Exception: define them in maro.utils.exception.
  • Docstring for class/function.
    • Definition of class/function.
    • Arguments explanation.
    • Returns explanation.
class XxxYyyZzz():
    """Summary of the class here.

    Details.

    Attributes:
        public_attribute_1: Introduction of this attribute.
        public_attribute_2: ...
        ...
    """

    def __init___(self, quota: int, is_test: bool):
        """One line summary.
        
        Details.

        Args:
            quota (int): Details.
                E.g. ...
            is_test (bool): Details.
            ...     
        """
        pass

    def run(self, para1: int, para2: bool = True):
        """One line summary.

        Details.

        Args:
            para1 (int): Details.
            para2 (bool): Details. Defaults to True.

        Returns:
            type: Details.
        """
        pass
  • Naming

    • File naming: xxx_yyy_zzz.py (noun structure)
      • Abstract class file naming: abs_xxx_yyy.py
    • Class naming: XxxYyyZzz (noun structure)
      • Abstract class naming: AbsXxxYyy
    • Function naming: verb/verb + noun
      • Private function: _xxx_yyy
      • Public function: xxx_yyy
  • Import

    • Import blocks order: native lib, 3rd party lib, privative lib.
    • In each import block, package are listed in alphabet order.
    • Between blocks, a blank line is needed.
    import io
    import os
    
    import numpy as np
    import pandas as pd
    
    from maro.backends.frame import FrameBase
    from maro.event_buffer import EventBuffer
  • File format: LF.