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 | |
Mike Frysinger | 40d5ecf | 2020-08-12 06:05:19 | [diff] [blame] | 148 | It is acceptable to mix f-strings & non-f-string formatting in a codebase, |
| 149 | module, function, or any other grouping. |
| 150 | Do not worry about having to pick only one style. |
| 151 | |
| 152 | ### f-strings |
| 153 | |
| 154 | When writing Python 3.6+ code, prefer [f-strings]. |
| 155 | They aren't always the best fit which is why we say you should *prefer* rather |
| 156 | than *always* use them. |
| 157 | See the next section for non-f-string formatting. |
| 158 | |
| 159 | Writing readable f-strings is important, so try to avoid packing too much logic |
| 160 | in the middle of the `{...}` sections. |
| 161 | If you need a list comprehension or complicated computation, then it's usually |
| 162 | better to pull it out into a temporary variable instead. |
| 163 | Dynamic code in the middle of static strings can be easy to miss for readers. |
| 164 | |
| 165 | ### % formatting |
| 166 | |
| 167 | The [Google style guide] says that the [% formatting] and `'{}'.format()` styles |
| 168 | are both permitted. |
| 169 | In CrOS we exclusively use [% formatting], so you should too to match. |
| 170 | There is no real material difference between the two styles, so sticking with |
| 171 | % for consistency is better. |
| 172 | |
| 173 | Changing either form to an f-string is always OK. |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 174 | |
Tom Hughes | c3b2b19 | 2019-07-31 16:23:29 | [diff] [blame] | 175 | ```python |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 176 | x = 'name: %s; score: %d' % (name, n) |
| 177 | x = 'name: %(name)s; score: %(score)d' % {'name': name, 'score': n} |
| 178 | ``` |
| 179 | |
Mike Frysinger | 40d5ecf | 2020-08-12 06:05:19 | [diff] [blame] | 180 | ### Logging formatting |
| 181 | |
| 182 | Keep in mind that for logging type functions, we don't format in-place. |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 183 | Instead, we pass them as args to the function. |
| 184 | |
Tom Hughes | c3b2b19 | 2019-07-31 16:23:29 | [diff] [blame] | 185 | ```python |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 186 | logging.info('name: %s; score: %d', name, n) |
| 187 | ``` |
| 188 | |
| 189 | ## Variables in Comprehensions & Generator Expressions |
| 190 | |
| 191 | When using comprehensions & generators, it's best if you stick to "throw away" |
| 192 | variable names. "x" tends to be the most common iterator. |
| 193 | |
Tom Hughes | c3b2b19 | 2019-07-31 16:23:29 | [diff] [blame] | 194 | ```python |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 195 | some_var = [x for x in some_list if x] |
| 196 | ``` |
| 197 | |
| 198 | While helping with readability (since these expressions are supposed to be kept |
| 199 | "simple"), it also helps avoid the problem of variable scopes not being as tight |
| 200 | as people expect. |
| 201 | |
Tom Hughes | c3b2b19 | 2019-07-31 16:23:29 | [diff] [blame] | 202 | ```python |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 203 | # This throws an exception because the variable is undefined. |
| 204 | print(x) |
| 205 | |
| 206 | some_var = [x for x in (1, 2)] |
| 207 | # This displays "1" because |x| is now defined. |
| 208 | print(x) |
| 209 | |
| 210 | string = 'some string' |
| 211 | some_var = [string for string in ('a', 'b', 'c')] |
| 212 | # This displays "c" because the earlier |string| value has been clobbered. |
| 213 | print(string) |
| 214 | ``` |
| 215 | |
| 216 | ## TODOs |
| 217 | |
| 218 | It is OK to leave TODOs in code. **Why?** Encouraging people to at least |
| 219 | document parts of code that need to be thought out more (or that are confusing) |
| 220 | is better than leaving this code undocumented. |
| 221 | |
| 222 | See also the [Google style guide] here. |
| 223 | |
| 224 | All TODOs should be formatted like: |
| 225 | |
Tom Hughes | c3b2b19 | 2019-07-31 16:23:29 | [diff] [blame] | 226 | ```python |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 227 | TODO(username): Revisit this code when the frob feature is done. |
| 228 | ``` |
| 229 | |
| 230 | ...where `username` is your chromium.org username. If you don't have a |
| 231 | chromium.org username, please use an email address. |
| 232 | |
| 233 | Please **do not** use other forms of TODO (like `TBD`, `FIXME`, `XXX`, etc). |
| 234 | This makes TODOs much harder for someone to find in the code. |
| 235 | |
| 236 | ## pylint |
| 237 | |
| 238 | Python code written for Chromium OS should be "pylint clean". |
| 239 | |
| 240 | [Pylint] is a utility that analyzes Python code to look for bugs and for style |
| 241 | violations. We run it in Chromium OS because: |
| 242 | |
| 243 | * It can catch bugs at "compile time" that otherwise wouldn't show up until |
| 244 | runtime. This can include typos and also places where you forgot to import |
| 245 | the required module. |
| 246 | * It helps to ensure that everyone is following the style guidelines. |
| 247 | |
| 248 | Pylint is notoriously picky and sometimes warns about things that are really OK. |
| 249 | Because of this, we: |
| 250 | |
| 251 | * Disable certain warnings in the Chromium OS pylintrc. |
Andrew | 62b7d4c | 2019-11-08 17:26:54 | [diff] [blame] | 252 | * Don't consider it a problem to locally disable warnings parts of code. |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 253 | **NOTE**: There should be a high bar for disabling warnings. |
| 254 | Specifically, you should first think about whether there's a way to rewrite |
| 255 | the code to avoid the warning before disabling it. |
| 256 | |
| 257 | You can use `cros lint` so that we can be sure everyone is on the same version. |
| 258 | This should work outside and inside the chroot. |
| 259 | |
| 260 | ## Other Considerations |
| 261 | |
| 262 | ### Unit tests |
| 263 | |
| 264 | All Python modules should have a corresponding unit test module called |
| 265 | `<original name>_unittest.py` in the same directory. We use a few related |
| 266 | modules (they're available in the chroot as well): |
| 267 | |
| 268 | * [mock] (`import mock`) for testing mocks |
| 269 | * It has become the official Python mock library starting in 3.3 |
| 270 | * [pymox] (`import mox`) is the old mock framework |
| 271 | |
| 272 | * You'll still see code using this as not all have been migrated to mock yet |
| 273 | |
| 274 | * [unittest] (`import unittest`) for unit test suites and methods for testing |
| 275 | |
Mike Frysinger | d035a20 | 2019-09-04 16:07:43 | [diff] [blame] | 276 | ### Main Modules {#main} |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 277 | |
Mike Frysinger | d035a20 | 2019-09-04 16:07:43 | [diff] [blame] | 278 | * All main modules require a `main` method. Use this boilerplate at the end: |
| 279 | ```python |
| 280 | if __name__ == '__main__': |
| 281 | sys.exit(main(sys.argv[1:])) |
| 282 | ``` |
| 283 | * Having `main` accept the arguments instead of going through the implicit |
| 284 | global `sys.argv` state allows for better reuse & unittesting: other |
| 285 | modules are able to import your module and then invoke it with |
| 286 | `main(['foo', 'bar'])`. |
| 287 | * The `sys.exit` allows `main` to return values which makes it easy to |
| 288 | unittest, and easier to reason about behavior in general: you can always |
| 289 | use `return` and not worry if you're in a func that expects a `sys.exit` |
| 290 | call. If your func doesn't explicitly return, then you still get the |
| 291 | default behavior of `sys.exit(0)`. |
| 292 | * If you want a stable string for prefixing output, you shouldn't rely on |
| 293 | `sys.argv[0]` at all as the caller can set that to whatever it wants (it |
| 294 | is never guaranteed to be the basename of your script). You should set |
| 295 | a constant in your module, or you should base it off the filename. |
| 296 | ```python |
| 297 | # A constant string that is guaranteed to never change. |
| 298 | _NAME = 'foo' |
| 299 | # The filename which should be stable (respects symlink names). |
| 300 | _NAME = os.path.basename(__file__) |
| 301 | # The filename which derefs symlinks. |
| 302 | _NAME = os.path.basename(os.path.realpath(__file__)) |
| 303 | ``` |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 304 | * If your main func wants `argv[0]`, you should use `sys.argv[0]` |
| 305 | directly. If you're trying to print an error message, then usually you |
| 306 | want to use `argparse` (see below) and the `parser.error()` helper |
| 307 | instead. |
| 308 | |
| 309 | * Main modules should have a symlink without an extension that point to the |
| 310 | .py file. All actual code should be in .py files. |
| 311 | |
| 312 | ### Other |
| 313 | |
| 314 | * There should be one blank line between conditional blocks. |
| 315 | * Do not use `GFlags` or `optparse` anymore -- stick to `argparse` everywhere. |
| 316 | |
Mike Frysinger | 9fc0fc0 | 2020-09-05 05:18:57 | [diff] [blame] | 317 | [Chromium coding style]: https://chromium.googlesource.com/chromium/src/+/HEAD/styleguide/styleguide.md |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 318 | [Google Python Style guide]: https://github.com/google/styleguide/blob/gh-pages/pyguide.md |
| 319 | [Indentation]: https://github.com/google/styleguide/blob/gh-pages/pyguide.md#s3.4-indentation |
| 320 | [Naming]: https://github.com/google/styleguide/blob/gh-pages/pyguide.md#316-naming |
| 321 | [PEP-8]: https://www.python.org/dev/peps/pep-0008/ |
| 322 | [Comments section]: https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings |
Mike Frysinger | 9fc0fc0 | 2020-09-05 05:18:57 | [diff] [blame] | 323 | [The CODING STYLE file for autotest]: https://chromium.googlesource.com/chromiumos/third_party/autotest/+/HEAD/docs/coding-style.md |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 324 | [reasonable syntax for arguments and return values]: https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings |
| 325 | [section on imports in the Google Style Guide]: https://github.com/google/styleguide/blob/gh-pages/pyguide.md#313-imports-formatting |
| 326 | [PEP-8]: https://www.python.org/dev/peps/pep-0008/ |
| 327 | [PEP-257]: https://www.python.org/dev/peps/pep-0257/ |
| 328 | [PEP-328]: https://www.python.org/dev/peps/pep-0328/ |
Mike Frysinger | 40d5ecf | 2020-08-12 06:05:19 | [diff] [blame] | 329 | [f-strings]: https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals |
Kirtika Ruchandani | 9124116 | 2019-07-20 00:19:31 | [diff] [blame] | 330 | [Google style guide]: https://github.com/google/styleguide/blob/gh-pages/pyguide.md#310-strings |
| 331 | [Google style guide]: https://github.com/google/styleguide/blob/gh-pages/pyguide.md#312-todo-comments |
| 332 | [mock]: https://docs.python.org/3/library/unittest.mock.html |
| 333 | [pymox]: https://code.google.com/p/pymox/wiki/MoxDocumentation |
| 334 | [unittest]: https://docs.python.org/library/unittest.html |
| 335 | [Pylint]: https://github.com/PyCQA/pylint |
Mike Frysinger | 40d5ecf | 2020-08-12 06:05:19 | [diff] [blame] | 336 | [% formatting]: https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting |