Mike Frysinger | 3e7f326 | 2020-08-11 19:49:26 | [diff] [blame^] | 1 | # Python Style Guidelines [go/cros-pystyle] |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 2 | |
| 3 | [TOC] |
| 4 | |
| 5 | ## Introduction |
| 6 | |
| 7 | You should follow this guide for all Python source code that is created as part |
| 8 | of the Chromium OS project i.e. src/platform/\*. You may not like some bits of |
| 9 | it, but you still need to follow it. In doing so, you ensure that others can |
| 10 | work with your code. |
| 11 | |
| 12 | This guide may be ignored if you are working on code that is part of an open |
| 13 | source project that already conforms to a different style i.e. generally Python |
| 14 | code in src/third\_party. Autotest is a great example of this - see the [other |
| 15 | notable Python style guides](#Other-notable-Python-style-guides) below. |
| 16 | |
| 17 | See also the overall [Chromium coding style]. |
| 18 | |
| 19 | ## Official Style Guide |
| 20 | |
| 21 | The [Google Python Style guide] is the official Python style guide for |
| 22 | Chromium OS original code. |
| 23 | |
| 24 | New projects should use that unmodified public Python Google style guide |
| 25 | (4 space indent with methods\_with\_underscores). Historically, we adopted a |
| 26 | style that was congruent with Google internal Python style guide (2 spaces with |
| 27 | MethodsAsCamelCase). Individual older projects are in the process of migrating |
| 28 | to the public style as time allows; there is no mandate to migrate. |
| 29 | |
| 30 | That is, for new projects, use: |
| 31 | |
| 32 | * [Indentation]: 4 spaces |
| 33 | * [Naming]: Functions and Method names use lower\_with\_under() |
| 34 | |
| 35 | If that guide is silent on an issue, then you should fall back to [PEP-8]. |
| 36 | |
| 37 | [PEP-257] is referenced by PEP-8. If it isn't covered in the |
| 38 | [Comments section], you can also fall back to PEP-257. |
| 39 | |
| 40 | ## Other notable Python style guides |
| 41 | |
| 42 | There are a number of other Python style guidelines out there. |
| 43 | |
| 44 | * [The CODING STYLE file for autotest] - When working on autotest code (or |
| 45 | any autotest tests), you should ignore the Chromium OS style guidelines |
| 46 | (AKA this page) and use the autotest ones. |
| 47 | |
| 48 | ## Describing arguments in docstrings |
| 49 | |
| 50 | [PEP-257] says that "The docstring for a function or method should summarize |
| 51 | its behavior and document its arguments, return value(s), side effects, |
| 52 | exceptions raised, and restrictions. However, it doesn't specify any details of |
| 53 | what this should look like. |
| 54 | |
| 55 | Fortunately, the Google Python style guide provides a [reasonable syntax for |
Mike Frysinger | 127243e | 2020-02-22 02:52:54 | [diff] [blame] | 56 | arguments and return values]. We will use that as our standard. |
| 57 | |
| 58 | The content of each subsection (e.g. `Args`, `Returns`, etc...) should use the |
| 59 | same indentation level as the file. If the file is using 2 spaces, then the |
| 60 | subsection content should use 2 space indents (as seen in the example below). |
| 61 | If the file is using 4 space indents, then the subsection content should use |
| 62 | 4 space indents as well. In both situations, use 4 space hanging indentation |
| 63 | for long argument descriptions (as seen with `keys:` below). |
| 64 | |
| 65 | All docstring content should use full sentences (proper capitalization & |
| 66 | periods), including argument descriptions (as seen below). This applies even |
| 67 | when the docstring is a one-liner. |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 68 | |
Tom Hughes | c3b2b19 | 2019-07-31 16:23:29 | [diff] [blame] | 69 | ```python |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 70 | def FetchBigtableRows(big_table, keys, other_silly_variable=None): |
| 71 | """Fetches rows from a Bigtable. |
| 72 | |
| 73 | Retrieves rows pertaining to the given keys from the Table instance |
| 74 | represented by big_table. Silly things may happen if |
| 75 | other_silly_variable is not None. |
| 76 | |
| 77 | Args: |
| 78 | big_table: An open Bigtable Table instance. |
| 79 | keys: A sequence of strings representing the key of each table row |
| 80 | to fetch. |
| 81 | other_silly_variable: Another optional variable, that has a much |
| 82 | longer name than the other args, and which does nothing. |
| 83 | |
| 84 | Returns: |
| 85 | A dict mapping keys to the corresponding table row data |
| 86 | fetched. Each row is represented as a tuple of strings. For |
| 87 | example: |
| 88 | |
| 89 | {'Serak': ('Rigel VII', 'Preparer'), |
| 90 | 'Zim': ('Irk', 'Invader'), |
| 91 | 'Lrrr': ('Omicron Persei 8', 'Emperor')} |
| 92 | |
| 93 | If a key from the keys argument is missing from the dictionary, |
| 94 | then that row was not found in the table. |
| 95 | |
| 96 | Raises: |
| 97 | IOError: An error occurred accessing the bigtable.Table object. |
| 98 | """ |
| 99 | pass |
| 100 | ``` |
| 101 | |
| 102 | Specifically: |
| 103 | |
| 104 | * **Required** of all **public methods** to follow this convention. |
| 105 | * **Encouraged** for all **private methods** to follow this convention. May |
| 106 | use a one-line sentence describing the function and return value if it's |
| 107 | simple. |
| 108 | * All arguments should be described in the `Args:` section, using the format |
| 109 | above. |
| 110 | * Ordering of descriptions should match order of parameters in function. |
| 111 | * For two-line descriptions, indent the 2nd line 4 spaces. |
Mike Frysinger | 127243e | 2020-02-22 02:52:54 | [diff] [blame] | 112 | * For functions, if present, `Examples:`, `Args:`, `Returns:` (or `Yields:` |
| 113 | with generators), and `Raises:` should be in that order and come last in the |
| 114 | docstring, each separated by a blank line. |
| 115 | * For classes, only `Examples:` and `Attributes:` are permitted. When the |
| 116 | the `__init__` method arguments line up with the class's attributes (e.g., |
| 117 | the function does a lot of `self.foo = foo`), there is no need to duplicate |
| 118 | argument documentation in the `__init__` docstring. Putting information in |
| 119 | the class's attributes is preferred. |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 120 | |
| 121 | ## Imports |
| 122 | |
| 123 | The following rules apply to imports (in addition to rules already talked about |
| 124 | in [PEP-8]): |
| 125 | |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 126 | * Relative imports are forbidden ([PEP-8] only "highly discourages" them). |
| 127 | Where absolutely needed, the `from __future__ import absolute_import` |
| 128 | syntax should be used (see [PEP-328]). |
| 129 | * Do not use `import *`. Always be explicit about what you are importing. |
| 130 | Using `import *` is useful for interactive Python but shouldn't be used in |
| 131 | checked-in code. |
| 132 | |
| 133 | ## The shebang line |
| 134 | |
| 135 | All files that are meant to be executed should start with the line: |
| 136 | |
Tom Hughes | c3b2b19 | 2019-07-31 16:23:29 | [diff] [blame] | 137 | ```python |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 138 | #!/usr/bin/env python2 |
| 139 | ``` |
| 140 | |
| 141 | If your system doesn't support that, it is sufficiently unusual for us to not |
| 142 | worry about compatibility with it. Most likely it will break in other fun ways. |
| 143 | |
| 144 | Files that are not meant to be executed should not contain a shebang line. |
| 145 | |
| 146 | ## String formatting |
| 147 | |
| 148 | The [Google style guide] says that the `'{}'.format()` style is permitted. |
| 149 | That is true, but in CrOS we much more often/always use % instead. You should |
| 150 | stick to using % in CrOS code to match existing codebases. |
| 151 | |
Tom Hughes | c3b2b19 | 2019-07-31 16:23:29 | [diff] [blame] | 152 | ```python |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 153 | x = 'name: %s; score: %d' % (name, n) |
| 154 | x = 'name: %(name)s; score: %(score)d' % {'name': name, 'score': n} |
| 155 | ``` |
| 156 | |
| 157 | Also keep in mind that for logging type functions, we don't format in place. |
| 158 | Instead, we pass them as args to the function. |
| 159 | |
Tom Hughes | c3b2b19 | 2019-07-31 16:23:29 | [diff] [blame] | 160 | ```python |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 161 | logging.info('name: %s; score: %d', name, n) |
| 162 | ``` |
| 163 | |
| 164 | ## Variables in Comprehensions & Generator Expressions |
| 165 | |
| 166 | When using comprehensions & generators, it's best if you stick to "throw away" |
| 167 | variable names. "x" tends to be the most common iterator. |
| 168 | |
Tom Hughes | c3b2b19 | 2019-07-31 16:23:29 | [diff] [blame] | 169 | ```python |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 170 | some_var = [x for x in some_list if x] |
| 171 | ``` |
| 172 | |
| 173 | While helping with readability (since these expressions are supposed to be kept |
| 174 | "simple"), it also helps avoid the problem of variable scopes not being as tight |
| 175 | as people expect. |
| 176 | |
Tom Hughes | c3b2b19 | 2019-07-31 16:23:29 | [diff] [blame] | 177 | ```python |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 178 | # This throws an exception because the variable is undefined. |
| 179 | print(x) |
| 180 | |
| 181 | some_var = [x for x in (1, 2)] |
| 182 | # This displays "1" because |x| is now defined. |
| 183 | print(x) |
| 184 | |
| 185 | string = 'some string' |
| 186 | some_var = [string for string in ('a', 'b', 'c')] |
| 187 | # This displays "c" because the earlier |string| value has been clobbered. |
| 188 | print(string) |
| 189 | ``` |
| 190 | |
| 191 | ## TODOs |
| 192 | |
| 193 | It is OK to leave TODOs in code. **Why?** Encouraging people to at least |
| 194 | document parts of code that need to be thought out more (or that are confusing) |
| 195 | is better than leaving this code undocumented. |
| 196 | |
| 197 | See also the [Google style guide] here. |
| 198 | |
| 199 | All TODOs should be formatted like: |
| 200 | |
Tom Hughes | c3b2b19 | 2019-07-31 16:23:29 | [diff] [blame] | 201 | ```python |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 202 | TODO(username): Revisit this code when the frob feature is done. |
| 203 | ``` |
| 204 | |
| 205 | ...where `username` is your chromium.org username. If you don't have a |
| 206 | chromium.org username, please use an email address. |
| 207 | |
| 208 | Please **do not** use other forms of TODO (like `TBD`, `FIXME`, `XXX`, etc). |
| 209 | This makes TODOs much harder for someone to find in the code. |
| 210 | |
| 211 | ## pylint |
| 212 | |
| 213 | Python code written for Chromium OS should be "pylint clean". |
| 214 | |
| 215 | [Pylint] is a utility that analyzes Python code to look for bugs and for style |
| 216 | violations. We run it in Chromium OS because: |
| 217 | |
| 218 | * It can catch bugs at "compile time" that otherwise wouldn't show up until |
| 219 | runtime. This can include typos and also places where you forgot to import |
| 220 | the required module. |
| 221 | * It helps to ensure that everyone is following the style guidelines. |
| 222 | |
| 223 | Pylint is notoriously picky and sometimes warns about things that are really OK. |
| 224 | Because of this, we: |
| 225 | |
| 226 | * Disable certain warnings in the Chromium OS pylintrc. |
Andrew | 62b7d4c | 2019-11-08 17:26:54 | [diff] [blame] | 227 | * Don't consider it a problem to locally disable warnings parts of code. |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 228 | **NOTE**: There should be a high bar for disabling warnings. |
| 229 | Specifically, you should first think about whether there's a way to rewrite |
| 230 | the code to avoid the warning before disabling it. |
| 231 | |
| 232 | You can use `cros lint` so that we can be sure everyone is on the same version. |
| 233 | This should work outside and inside the chroot. |
| 234 | |
| 235 | ## Other Considerations |
| 236 | |
| 237 | ### Unit tests |
| 238 | |
| 239 | All Python modules should have a corresponding unit test module called |
| 240 | `<original name>_unittest.py` in the same directory. We use a few related |
| 241 | modules (they're available in the chroot as well): |
| 242 | |
| 243 | * [mock] (`import mock`) for testing mocks |
| 244 | * It has become the official Python mock library starting in 3.3 |
| 245 | * [pymox] (`import mox`) is the old mock framework |
| 246 | |
| 247 | * You'll still see code using this as not all have been migrated to mock yet |
| 248 | |
| 249 | * [unittest] (`import unittest`) for unit test suites and methods for testing |
| 250 | |
Mike Frysinger | d035a20 | 2019-09-04 16:07:43 | [diff] [blame] | 251 | ### Main Modules {#main} |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 252 | |
Mike Frysinger | d035a20 | 2019-09-04 16:07:43 | [diff] [blame] | 253 | * All main modules require a `main` method. Use this boilerplate at the end: |
| 254 | ```python |
| 255 | if __name__ == '__main__': |
| 256 | sys.exit(main(sys.argv[1:])) |
| 257 | ``` |
| 258 | * Having `main` accept the arguments instead of going through the implicit |
| 259 | global `sys.argv` state allows for better reuse & unittesting: other |
| 260 | modules are able to import your module and then invoke it with |
| 261 | `main(['foo', 'bar'])`. |
| 262 | * The `sys.exit` allows `main` to return values which makes it easy to |
| 263 | unittest, and easier to reason about behavior in general: you can always |
| 264 | use `return` and not worry if you're in a func that expects a `sys.exit` |
| 265 | call. If your func doesn't explicitly return, then you still get the |
| 266 | default behavior of `sys.exit(0)`. |
| 267 | * If you want a stable string for prefixing output, you shouldn't rely on |
| 268 | `sys.argv[0]` at all as the caller can set that to whatever it wants (it |
| 269 | is never guaranteed to be the basename of your script). You should set |
| 270 | a constant in your module, or you should base it off the filename. |
| 271 | ```python |
| 272 | # A constant string that is guaranteed to never change. |
| 273 | _NAME = 'foo' |
| 274 | # The filename which should be stable (respects symlink names). |
| 275 | _NAME = os.path.basename(__file__) |
| 276 | # The filename which derefs symlinks. |
| 277 | _NAME = os.path.basename(os.path.realpath(__file__)) |
| 278 | ``` |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 279 | * If your main func wants `argv[0]`, you should use `sys.argv[0]` |
| 280 | directly. If you're trying to print an error message, then usually you |
| 281 | want to use `argparse` (see below) and the `parser.error()` helper |
| 282 | instead. |
| 283 | |
| 284 | * Main modules should have a symlink without an extension that point to the |
| 285 | .py file. All actual code should be in .py files. |
| 286 | |
| 287 | ### Other |
| 288 | |
| 289 | * There should be one blank line between conditional blocks. |
| 290 | * Do not use `GFlags` or `optparse` anymore -- stick to `argparse` everywhere. |
| 291 | |
| 292 | [Chromium coding style]: https://chromium.googlesource.com/chromium/src/+/master/styleguide/styleguide.md |
| 293 | [Google Python Style guide]: https://github.com/google/styleguide/blob/gh-pages/pyguide.md |
| 294 | [Indentation]: https://github.com/google/styleguide/blob/gh-pages/pyguide.md#s3.4-indentation |
| 295 | [Naming]: https://github.com/google/styleguide/blob/gh-pages/pyguide.md#316-naming |
| 296 | [PEP-8]: https://www.python.org/dev/peps/pep-0008/ |
| 297 | [Comments section]: https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings |
| 298 | [The CODING STYLE file for autotest]: https://chromium.googlesource.com/chromiumos/third_party/autotest/+/master/docs/coding-style.md |
| 299 | [reasonable syntax for arguments and return values]: https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings |
| 300 | [section on imports in the Google Style Guide]: https://github.com/google/styleguide/blob/gh-pages/pyguide.md#313-imports-formatting |
| 301 | [PEP-8]: https://www.python.org/dev/peps/pep-0008/ |
| 302 | [PEP-257]: https://www.python.org/dev/peps/pep-0257/ |
| 303 | [PEP-328]: https://www.python.org/dev/peps/pep-0328/ |
| 304 | [Google style guide]: https://github.com/google/styleguide/blob/gh-pages/pyguide.md#310-strings |
| 305 | [Google style guide]: https://github.com/google/styleguide/blob/gh-pages/pyguide.md#312-todo-comments |
| 306 | [mock]: https://docs.python.org/3/library/unittest.mock.html |
| 307 | [pymox]: https://code.google.com/p/pymox/wiki/MoxDocumentation |
| 308 | [unittest]: https://docs.python.org/library/unittest.html |
| 309 | [Pylint]: https://github.com/PyCQA/pylint |