Garret Kelly | 9eebde0 | 2019-10-22 15:36:49 -0400 | [diff] [blame] | 1 | --- |
| 2 | title: "Python Coding Style Guide" |
| 3 | --- |
lowRISC Contributors | 802543a | 2019-08-31 12:12:56 +0100 | [diff] [blame] | 4 | |
| 5 | ## Basics |
| 6 | |
| 7 | ### Summary |
| 8 | |
| 9 | Python3 is the main language used for simple tools. |
| 10 | |
| 11 | Python can be written in vastly different styles, which can lead to code conflicts and code review latency. |
| 12 | This style guide aims to promote Python readability across groups. |
| 13 | To quote the C++ style guide: "Creating common, required idioms and patterns makes code much easier to understand." |
| 14 | |
| 15 | This guide defines the lowRISC style for Python version 3. |
| 16 | The goals are to: |
| 17 | |
| 18 | * promote consistency across hardware development projects |
| 19 | * promote best practices |
| 20 | * increase code sharing and re-use |
| 21 | |
lowRISC Contributors | 802543a | 2019-08-31 12:12:56 +0100 | [diff] [blame] | 22 | |
| 23 | ### Terminology Conventions |
| 24 | |
| 25 | Unless otherwise noted, the following terminology conventions apply to this style guide: |
| 26 | |
| 27 | * The word ***must*** indicates a mandatory requirement. |
| 28 | Similarly, ***do not*** indicates a prohibition. |
| 29 | Imperative and declarative statements correspond to ***must***. |
| 30 | * The word ***recommended*** indicates that a certain course of action is preferred or is most suitable. |
| 31 | Similarly, ***not recommended*** indicates that a course of action is unsuitable, but not prohibited. |
| 32 | There may be reasons to use other options, but the implications and reasons for doing so must be fully understood. |
| 33 | * The word ***may*** indicates a course of action is permitted and optional. |
| 34 | * The word ***can*** indicates a course of action is possible given material, physical, or causal constraints. |
| 35 | |
| 36 | ### Style Guide Exceptions |
| 37 | |
| 38 | ***Justify exceptions with a comment.*** |
| 39 | |
| 40 | No style guide is perfect. |
| 41 | There are times when the best path to a working design, or for working around a tool issue, is to simply cut the Gordian Knot and create code that is at variance with this style guide. |
| 42 | It is always okay to deviate from the style guide by necessity, as long as that necessity is clearly justified by a brief comment, as well as a lint waiver pragma where appropriate. |
| 43 | |
| 44 | A common case where you may wish to disable tool-enforced reformatting is for large manually formatted data literals. |
| 45 | In this case, no explanatory comment is required and yapf can be disabled for that literal [with a single pragma](https://github.com/google/yapf#why-does-yapf-destroy-my-awesome-formatting). |
| 46 | |
| 47 | ## Python Conventions |
| 48 | |
| 49 | ### Summary |
| 50 | |
| 51 | The lowRISC style matches [PEP8](https://www.python.org/dev/peps/pep-0008/) with the following options: |
| 52 | * Bitwise operators should be placed before a line split |
| 53 | * Logical operators should be placed before a line split |
| 54 | |
| 55 | To avoid doubt, the interpretation of PEP8 is done by [yapf](https://github.com/google/yapf) and the style guide is set using a `.style.yapf` file in the top level directory of the repository. |
| 56 | This just sets the base style to pep8 and overrides with the exceptions given above. |
| 57 | |
| 58 | In addition to the basic style, imports must be ordered alphabetically within sections: |
| 59 | * Future |
| 60 | * Python Standard Library |
| 61 | * Third Party |
| 62 | * Current Python Project |
| 63 | |
| 64 | The import ordering matches that enforced by [isort](https://github.com/timothycrosley/isort). |
| 65 | Currently the `isort` defaults are used. |
| 66 | If this changes a `.isort.cfg` file will be placed in the top level directory of the repository. |
| 67 | |
| 68 | ### Lint tool |
| 69 | |
Philipp Wagner | 14a3fee | 2019-11-21 10:07:02 +0000 | [diff] [blame] | 70 | The `lintpy.py` utility in `util` can be used to check Python code. |
| 71 | It checks all Python (`.py`) files that are modified in the local repository and will report problems. |
lowRISC Contributors | 802543a | 2019-08-31 12:12:56 +0100 | [diff] [blame] | 72 | Both `yapf` and `isort` checks are run. |
| 73 | |
| 74 | Basic lintpy usage is just to run from the util directory. |
| 75 | If everything is fine the command produces no output, otherwise it will report the problems. |
| 76 | Additional information will be printed if the `--verbose` or `-v` flag is given. |
| 77 | |
| 78 | ```console |
| 79 | $ cd $REPO_TOP/util |
| 80 | $ ./lintpy.py |
| 81 | $ ./lintpy.py -v |
| 82 | ``` |
| 83 | |
| 84 | Checking can be done on an explicit list of files using the `--file` or `-f` flag. |
| 85 | In this case the tool will not derive the list from git, so any file can be checked even if it has not been modified. |
| 86 | |
| 87 | ```console |
| 88 | $ cd $REPO_TOP/util |
| 89 | $ ./lintpy.py -f a.py subdir/*.py |
| 90 | ``` |
| 91 | |
| 92 | Errors may be fixed using the same tool to edit the problem file(s) in-place (you may need to refresh the file(s) in your editor after doing this). |
Philipp Wagner | 14a3fee | 2019-11-21 10:07:02 +0000 | [diff] [blame] | 93 | This uses the same set of files as are being checked, so unless the`--file` or `-f` flag is used this will only affect files that have already been modifed (or staged for commit if `-c`is used) and will not fix errors in Python files that have not been touched. |
lowRISC Contributors | 802543a | 2019-08-31 12:12:56 +0100 | [diff] [blame] | 94 | |
| 95 | ```console |
| 96 | $ cd $REPO_TOP/util |
| 97 | $ ./lintpy.py --fix |
| 98 | ``` |
| 99 | |
| 100 | lintpy.py can be installed as a git pre-commit hook which will prevent commits if there are any lint errors. |
| 101 | This will normally be a symlink to the tool in util so changes are automatically used (it also works if `lintpy.py` is copied to `.git/hooks/pre-commit` but in that case the hook must be reinstalled each time the tool changes). |
| 102 | Since git hooks are not automatically installed the symlink hook can be installed if required using the tool: |
| 103 | |
| 104 | ```console |
| 105 | $ cd $REPO_TOP/util |
| 106 | $ ./lintpy.py --hook |
| 107 | ``` |
| 108 | |
| 109 | |
| 110 | Fixing style errors for a single file can also be done with `yapf` directly: |
| 111 | ```console |
| 112 | $ yapf -i file.py |
| 113 | ``` |
| 114 | |
| 115 | Fixing import ordering errors for a single file can be done with `isort`: |
| 116 | ```console |
| 117 | $ isort file.py |
| 118 | ``` |
| 119 | |
Philipp Wagner | 14a3fee | 2019-11-21 10:07:02 +0000 | [diff] [blame] | 120 | Yapf and isort are Python packages and should be installed with pip: |
lowRISC Contributors | 802543a | 2019-08-31 12:12:56 +0100 | [diff] [blame] | 121 | |
| 122 | ```console |
| 123 | $ pip3 install --user yapt |
| 124 | $ pip3 install --user isort |
| 125 | ``` |
| 126 | |
| 127 | ### File Extensions |
| 128 | |
| 129 | ***Use the `.py` extension for Python files*** |
| 130 | |
| 131 | ### General File Appearance |
| 132 | |
| 133 | #### Characters |
| 134 | |
| 135 | ***Use only UTF-8 characters with UNIX-style line endings(`"\n"`).*** |
| 136 | |
| 137 | Follows PEP8. |
| 138 | |
| 139 | #### POSIX File Endings |
| 140 | |
| 141 | ***All lines on non-empty files must end with a newline (`"\n"`).*** |
| 142 | |
| 143 | #### Line Length |
| 144 | |
| 145 | ***Wrap the code at 79 characters per line.*** |
| 146 | |
| 147 | The maximum line length follows PEP8. |
| 148 | |
| 149 | Exceptions: |
| 150 | |
| 151 | - Any place where line wraps are impossible (for example, an include path might extend past 79 characters). |
| 152 | |
| 153 | #### No Tabs |
| 154 | |
| 155 | ***Do not use tabs anywhere.*** |
| 156 | |
| 157 | Use spaces to indent or align text. |
| 158 | |
| 159 | To convert tabs to spaces on any file, you can use the [UNIX `expand`](http://linux.die.net/man/1/expand) utility. |
| 160 | |
| 161 | #### No Trailing Spaces |
| 162 | |
| 163 | ***Delete trailing whitespace at the end of lines.*** |
| 164 | |
| 165 | ### Indentation |
| 166 | |
| 167 | ***Indentation is four spaces per level.*** |
| 168 | |
| 169 | Follows PEP8. |
| 170 | Use spaces for indentation. |
| 171 | Do not use tabs. |
| 172 | You should set your editor to emit spaces when you hit the tab key. |
| 173 | |
Philipp Wagner | 14a3fee | 2019-11-21 10:07:02 +0000 | [diff] [blame] | 174 | ### Executable Python tools |
lowRISC Contributors | 802543a | 2019-08-31 12:12:56 +0100 | [diff] [blame] | 175 | |
Philipp Wagner | 14a3fee | 2019-11-21 10:07:02 +0000 | [diff] [blame] | 176 | Tools that can be executed should use `env` to avoid making assumptions about the location of the Python interpreter. |
lowRISC Contributors | 802543a | 2019-08-31 12:12:56 +0100 | [diff] [blame] | 177 | Thus they should begin with the line: |
| 178 | |
| 179 | ```console |
| 180 | #!/usr/bin/env python3 |
| 181 | ``` |
| 182 | |
| 183 | This should be followed by a comment with the license information and the doc string describing the command. |
| 184 | |
| 185 | #### Argument Parsing |
| 186 | |
| 187 | ***Use argparse to parse command line arguments.*** |
| 188 | |
| 189 | In command line tools use the [argparse library](https://docs.python.org/3/library/argparse.html) to parse arguments. |
| 190 | This will provide support for `--help` and `-h` to get usage information. |
| 191 | |
| 192 | Every command line program should provide `--version` to provide standard version information. |
Philipp Wagner | 14a3fee | 2019-11-21 10:07:02 +0000 | [diff] [blame] | 193 | This lists the git repositary information for the tool and the version numbers of any Python packages that are used. |
lowRISC Contributors | 802543a | 2019-08-31 12:12:56 +0100 | [diff] [blame] | 194 | The `show_and_exit` routine in `reggen/version.py` can be used to do this. |