diff --git a/pyguide.md b/pyguide.md index 0efe667..7090e69 100644 --- a/pyguide.md +++ b/pyguide.md @@ -75,7 +75,7 @@ See README.md for details. + [3.19.7 Ignoring Types](#s3.19.7-ignoring-types) + [3.19.8 Typing Variables](#s3.19.8-typing-variables) + [3.19.9 Tuples vs Lists](#s3.19.9-tuples-vs-lists) - + [3.19.10 TypeVars](#s3.19.10-typevars) + + [3.19.10 Type variables](#s3.19.10-typevars) + [3.19.11 String types](#s3.19.11-string-types) + [3.19.12 Imports For Typing](#s3.19.12-imports-for-typing) + [3.19.13 Conditional Imports](#s3.19.13-conditional-imports) @@ -97,7 +97,7 @@ of *dos and don'ts* for Python programs. To help you format code correctly, we've created a [settings file for Vim](google_python_style.vim). For Emacs, the default settings should be fine. -Many teams use the [yapf](https://github.com/google/yapf/) +Many teams use the [Black](https://github.com/psf/black) or [Pyink](https://github.com/google/pyink) auto-formatter to avoid arguing over formatting. @@ -161,7 +161,8 @@ Suppress warnings if they are inappropriate so that other issues are not hidden. To suppress warnings, you can set a line-level comment: ```python -dict = 'something awful' # Bad Idea... pylint: disable=redefined-builtin +def do_PUT(self): # WSGI name, so pylint: disable=invalid-name + ... ``` `pylint` @@ -185,7 +186,7 @@ pylint --list-msgs To get more information on a particular message, use: ```shell -pylint --help-msg=C6409 +pylint --help-msg=invalid-name ``` Prefer `pylint: disable` to the deprecated older form `pylint: disable-msg`. @@ -195,7 +196,7 @@ beginning of the function. Always include a comment explaining why you are deleting it. "Unused." is sufficient. For example: ```python -def viking_cafe_order(spam: str, beans: str, eggs: Optional[str] = None) -> str: +def viking_cafe_order(spam: str, beans: str, eggs: str | None = None) -> str: del beans, eggs # Unused by vikings. return spam + spam + spam ``` @@ -347,7 +348,7 @@ No: The directory the main binary is located in should not be assumed to be in `sys.path` despite that happening in some environments. This being the case, -code should assume that `import jodie` refers to a third party or top level +code should assume that `import jodie` refers to a third-party or top-level package named `jodie`, not a local `jodie.py`. @@ -494,7 +495,7 @@ Avoid mutable global state. #### 2.5.1 Definition -Module level values or class attributes that can get mutated during program +Module-level values or class attributes that can get mutated during program execution. @@ -568,8 +569,8 @@ variables defined in enclosing scopes. Allows definition of utility classes and functions that are only used inside of a very limited scope. Very -[ADT](http://www.google.com/url?sa=D&q=http://en.wikipedia.org/wiki/Abstract_data_type)-y. -Commonly used for implementing decorators. +[ADT](https://en.wikipedia.org/wiki/Abstract_data_type)-y. Commonly used for +implementing decorators. @@ -973,7 +974,7 @@ definition. Yes: def foo(a, b=None): if b is None: b = [] -Yes: def foo(a, b: Optional[Sequence] = None): +Yes: def foo(a, b: Sequence | None = None): if b is None: b = [] Yes: def foo(a, b: Sequence = ()): # Empty tuple OK since tuples are immutable. @@ -1197,8 +1198,8 @@ experienced Lisp and Scheme (and Haskell and ML and ...) programmers. #### 2.16.3 Cons -Can lead to confusing bugs. Such as this example based on -[PEP-0227](http://www.google.com/url?sa=D&q=http://www.python.org/dev/peps/pep-0227/): +Can lead to confusing bugs, such as this example based on +[PEP-0227](https://peps.python.org/pep-0227/): ```python i = 4 @@ -1300,11 +1301,11 @@ decorator runs (at import time, perhaps from `pydoc` or other tools). A decorator that is called with valid parameters should (as much as possible) be guaranteed to succeed in all cases. -Decorators are a special case of "top level code" - see [main](#s3.17-main) for +Decorators are a special case of "top-level code" - see [main](#s3.17-main) for more discussion. Never use `staticmethod` unless forced to in order to integrate with an API -defined in an existing library. Write a module level function instead. +defined in an existing library. Write a module-level function instead. Use `classmethod` only when writing a named constructor, or a class-specific routine that modifies necessary global state such as a process-wide cache. @@ -1323,8 +1324,8 @@ or `__eq__` are implemented as Python methods) and their atomicity should not be relied upon. Neither should you rely on atomic variable assignment (since this in turn depends on dictionaries). -Use the Queue module's `Queue` data type as the preferred way to communicate -data between threads. Otherwise, use the threading module and its locking +Use the `queue` module's `Queue` data type as the preferred way to communicate +data between threads. Otherwise, use the `threading` module and its locking primitives. Prefer condition variables and `threading.Condition` instead of using lower-level locks. @@ -1460,11 +1461,11 @@ Use other `from __future__` import statements as you see fit. ### 2.21 Type Annotated Code You can annotate Python code with type hints according to -[PEP-484](https://www.python.org/dev/peps/pep-0484/), and type-check the code at -build time with a type checking tool like [pytype](https://github.com/google/pytype). +[PEP-484](https://peps.python.org/pep-0484/), and type-check the code at build +time with a type checking tool like [pytype](https://github.com/google/pytype). Type annotations can be in the source or in a -[stub pyi file](https://www.python.org/dev/peps/pep-0484/#stub-files). Whenever +[stub pyi file](https://peps.python.org/pep-0484/#stub-files). Whenever possible, annotations should be in the source. Use pyi files for third-party or extension modules. @@ -1483,7 +1484,7 @@ def func(a: int) -> list[int]: ``` You can also declare the type of a variable using similar -[PEP-526](https://www.python.org/dev/peps/pep-0526/) syntax: +[PEP-526](https://peps.python.org/pep-0526/) syntax: ```python a: SomeType = some_func() @@ -1554,23 +1555,53 @@ Explicit exceptions to the 80 character limit: - Long import statements. - URLs, pathnames, or long flags in comments. -- Long string module level constants not containing whitespace that would be +- Long string module-level constants not containing whitespace that would be inconvenient to split across lines such as URLs or pathnames. - Pylint disable comments. (e.g.: `# pylint: disable=invalid-name`) -Do not use backslash line continuation except for `with` statements requiring -three or more context managers. +Do not use a backslash for +[explicit line continuation](https://docs.python.org/3/reference/lexical_analysis.html#explicit-line-joining). -Make use of Python's +Instead, make use of Python's [implicit line joining inside parentheses, brackets and braces](http://docs.python.org/reference/lexical_analysis.html#implicit-line-joining). If necessary, you can add an extra pair of parentheses around an expression. +Note that this rule doesn't prohibit backslash-escaped newlines within strings +(see [below](#strings)). + ```python Yes: foo_bar(self, width, height, color='black', design=None, x='foo', emphasis=None, highlight=0) +``` - if (width == 0 and height == 0 and +```python + +Yes: if (width == 0 and height == 0 and color == 'red' and emphasis == 'strong'): + + (bridge_questions.clarification_on + .average_airspeed_of.unladen_swallow) = 'African or European?' + + with ( + very_long_first_expression_function() as spam, + very_long_second_expression_function() as beans, + third_thing() as eggs, + ): + place_order(eggs, beans, spam, beans) +``` + +```python + +No: if width == 0 and height == 0 and \ + color == 'red' and emphasis == 'strong': + + bridge_questions.clarification_on \ + .average_airspeed_of.unladen_swallow = 'African or European?' + + with very_long_first_expression_function() as spam, \ + very_long_second_expression_function() as beans, \ + third_thing() as eggs: + place_order(eggs, beans, spam, beans) ``` When a literal string won't fit on a single line, use parentheses for implicit @@ -1581,6 +1612,37 @@ x = ('This will build a very long long ' 'long long long long long long string') ``` +Prefer to break lines at the highest possible syntactic level. If you must break +a line twice, break it at the same syntactic level both times. + +```python +Yes: bridgekeeper.answer( + name="Arthur", quest=questlib.find(owner="Arthur", perilous=True)) + + answer = (a_long_line().of_chained_methods() + .that_eventually_provides().an_answer()) + + if ( + config is None + or 'editor.language' not in config + or config['editor.language'].use_spaces is False + ): + use_tabs() +``` + +```python +No: bridgekeeper.answer(name="Arthur", quest=questlib.find( + owner="Arthur", perilous=True)) + + answer = a_long_line().of_chained_methods().that_eventually_provides( + ).an_answer() + + if (config is None or 'editor.language' not in config or config[ + 'editor.language'].use_spaces is False): + use_tabs() + +``` + Within comments, put long URLs on their own line if necessary. ```python @@ -1594,29 +1656,6 @@ No: # See details at # v2.0/csv_file_name_extension_full_specification.html ``` -It is permissible to use backslash continuation when defining a `with` statement -with three or more context managers. For two context managers, use a nested -`with` statement: - -```python -Yes: with very_long_first_expression_function() as spam, \ - very_long_second_expression_function() as beans, \ - third_thing() as eggs: - place_order(eggs, beans, spam, beans) -``` - -```python -No: with VeryLongFirstExpressionFunction() as spam, \ - VeryLongSecondExpressionFunction() as beans: - PlaceOrder(beans, spam) -``` - -```python -Yes: with very_long_first_expression_function() as spam: - with very_long_second_expression_function() as beans: - place_order(beans, spam) -``` - Make note of the indentation of the elements in the line continuation examples above; see the [indentation](#s3.4-indentation) section for explanation. @@ -1753,151 +1792,7 @@ No: # Stuff on first line forbidden. Trailing commas in sequences of items are recommended only when the closing container token `]`, `)`, or `}` does not appear on the same line as the final element. The presence of a trailing comma is also used as a hint to our Python -code auto-formatter [YAPF](https://pypi.org/project/yapf/) to direct it to auto-format the container -of items to one item per line when the `,` after the final element is present. - -```python -Yes: golomb3 = [0, 1, 3] -Yes: golomb4 = [ - 0, - 1, - 4, - 6, - ] -``` - -```python -No: golomb4 = [ - 0, - 1, - 4, - 6 - ] -``` - - - - - -### 3.5 Blank Lines - -Two blank lines between top-level definitions, be they function or class -definitions. One blank line between method definitions and between the docstring -of a `class` and the first method. No blank line following a `def` line. Use -single blank lines as you judge appropriate within functions or methods. - -Blank lines need not be anchored to the definition. For example, related -comments immediately preceding function, class, and method definitions can make -sense. Consider if your comment might be more useful as part of the docstring. - - - - - -### 3.6 Whitespace - -Follow standard typographic rules for the use of spaces around punctuation. - -No whitespace inside parentheses, brackets or braces. - -```python -Yes: spam(ham[1], {'eggs': 2}, []) -``` - -```python -No: spam( ham[ 1 ], { 'eggs': 2 }, [ ] ) -``` - -No whitespace before a comma, semicolon, or colon. Do use whitespace after a -comma, semicolon, or colon, except at the end of the line. - -```python -Yes: if x == 4: - print(x, y) - x, y = y, x -``` - -```python -No: if x == 4 : - print(x , y) - x , y = y , x -``` - -No whitespace before the open paren/bracket that starts an argument list, -indexing or slicing. - -```python -Yes: spam(1) -``` - -```python -No: spam (1) -``` - -```python -Yes: dict['key'] = list[index] -``` - -```python -No: dict ['key'] = list [index] -``` - -No trailing whitespace. - -Surround binary operators with a single space on either side for assignment -(`=`), comparisons (`==, <, >, !=, <>, <=, >=, in, not in, is, is not`), and -Booleans (`and, or, not`). Use your better judgment for the insertion of spaces -around arithmetic operators (`+`, `-`, `*`, `/`, `//`, `%`, `**`, `@`). - -```python -Yes: x == 1 -``` - -```python -No: x<1 -``` - -Never use spaces around `=` when passing keyword arguments or defining a default -parameter value, with one exception: -[when a type annotation is present](#typing-default-values), *do* use spaces -around the `=` for the default parameter value. - -```python -Yes: def complex(real, imag=0.0): return Magic(r=real, i=imag) -Yes: def complex(real, imag: float = 0.0): return Magic(r=real, i=imag) -``` - -```python -No: def complex(real, imag = 0.0): return Magic(r = real, i = imag) -No: def complex(real, imag: float=0.0): return Magic(r = real, i = imag) -``` - -Don't use spaces to vertically align tokens on consecutive lines, since it -becomes a maintenance burden (applies to `:`, `#`, `=`, etc.): - -```python -Yes: - foo = 1000 # comment - long_name = 2 # comment that should not be aligned - - dictionary = { - 'foo': 1, - 'long_name': 2, - } -``` - -```python -No: - foo = 1000 # comment - long_name = 2 # comment that should not be aligned - - dictionary = { - 'foo' : 1, - 'long_name': 2, - } -``` - - +code auto-formatter @@ -1908,7 +1803,7 @@ No: Most `.py` files do not need to start with a `#!` line. Start the main file of a program with `#!/usr/bin/env python3` (to support virtualenvs) or `#!/usr/bin/python3` per -[PEP-394](https://www.python.org/dev/peps/pep-0394/). +[PEP-394](https://peps.python.org/pep-0394/). This line is used by the kernel to find the Python interpreter, but is ignored by Python when importing modules. It is only necessary on a file intended to be executed directly. @@ -1933,15 +1828,14 @@ Python uses *docstrings* to document code. A docstring is a string that is the first statement in a package, module, class or function. These strings can be extracted automatically through the `__doc__` member of the object and are used by `pydoc`. -(Try running `pydoc` on your module to see how it looks.) Always use the three -double-quote `"""` format for docstrings (per -[PEP 257](https://www.google.com/url?sa=D&q=http://www.python.org/dev/peps/pep-0257/)). -A docstring should be organized as a summary line (one physical line not -exceeding 80 characters) terminated by a period, question mark, or exclamation -point. When writing more (encouraged), this must be followed by a blank line, -followed by the rest of the docstring starting at the same cursor position as -the first quote of the first line. There are more formatting guidelines for -docstrings below. +(Try running `pydoc` on your module to see how it looks.) Always use the +three-double-quote `"""` format for docstrings (per +[PEP 257](https://peps.python.org/pep-0257/)). A docstring should be organized +as a summary line (one physical line not exceeding 80 characters) terminated by +a period, question mark, or exclamation point. When writing more (encouraged), +this must be followed by a blank line, followed by the rest of the docstring +starting at the same cursor position as the first quote of the first line. There +are more formatting guidelines for docstrings below. @@ -1950,12 +1844,12 @@ docstrings below. #### 3.8.2 Modules -Every file should contain license boilerplate. Choose the appropriate boilerplate for the license used by the project (for example, Apache 2.0, BSD, LGPL, GPL) +Every file should contain license boilerplate. Choose the appropriate boilerplate for the license used by the project (for example, Apache 2.0, BSD, LGPL, GPL). Files should start with a docstring describing the contents and usage of the module. ```python -"""A one line summary of the module or program, terminated by a period. +"""A one-line summary of the module or program, terminated by a period. Leave one blank line. The rest of this docstring should contain an overall description of the module or program. Optionally, it may also @@ -2081,9 +1975,10 @@ aptly described using a one-line docstring. part of the API). ```python -def fetch_smalltable_rows(table_handle: smalltable.Table, - keys: Sequence[Union[bytes, str]], - require_all_keys: bool = False, +def fetch_smalltable_rows( + table_handle: smalltable.Table, + keys: Sequence[bytes | str], + require_all_keys: bool = False, ) -> Mapping[bytes, tuple[str, ...]]: """Fetches rows from a Smalltable. @@ -2118,9 +2013,10 @@ def fetch_smalltable_rows(table_handle: smalltable.Table, Similarly, this variation on `Args:` with a line break is also allowed: ```python -def fetch_smalltable_rows(table_handle: smalltable.Table, - keys: Sequence[Union[bytes, str]], - require_all_keys: bool = False, +def fetch_smalltable_rows( + table_handle: smalltable.Table, + keys: Sequence[bytes | str], + require_all_keys: bool = False, ) -> Mapping[bytes, tuple[str, ...]]: """Fetches rows from a Smalltable. @@ -2179,7 +2075,11 @@ class SampleClass: """ def __init__(self, likes_spam: bool = False): - """Inits SampleClass with blah.""" + """Initializes the instance based on spam preference. + + Args: + likes_spam: Defines if instance exhibits this preference. + """ self.likes_spam = likes_spam self.eggs = 0 @@ -2293,6 +2193,7 @@ Yes: x = f'name: {name}; score: {n}' x = '%s, %s!' % (imperative, expletive) x = '{}, {}'.format(first, second) x = 'name: %s; score: %d' % (name, n) + x = 'name: %(name)s; score: %(score)d' % {'name':name, 'score':n} x = 'name: {}; score: {}'.format(name, n) x = a + b ``` @@ -2309,7 +2210,7 @@ be optimized on CPython, that is an implementation detail. The conditions under which an optimization applies are not easy to predict and may change. Instead, add each substring to a list and `''.join` the list after the loop terminates, or write each substring to an `io.StringIO` buffer. These techniques -consistently have amortized-linear run time complexity. +consistently have amortized-linear run-time complexity. ```python Yes: items = ['