Python Style Reference¶
Overview | Guide | Best Practices | Reference
Complete style reference adapted from Google's Python style guide. For quick project conventions, see the Guide.
Tooling Note: This reference mentions
ruff,Black, andPyink. At Slurmq, we use modern equivalents:
- Linting/Formatting: ruff (replaces ruff, Black, isort, Flake8)
- Type Checking: ty now, ty at beta
- Project Management: uv (replaces pip, poetry, virtualenv)
The style principles in this document still apply; only the tools have changed.
Table of Contents
- [1 Background](#s1-background) - [2 Python Language Rules](#s2-python-language-rules) - [2.1 Lint](#s2.1-lint) - [2.2 Imports](#s2.2-imports) - [2.3 Packages](#s2.3-packages) - [2.4 Exceptions](#s2.4-exceptions) - [2.5 Mutable Global State](#s2.5-global-variables) - [2.6 Nested/Local/Inner Classes and Functions](#s2.6-nested) - [2.7 Comprehensions & Generator Expressions](#s2.7-comprehensions) - [2.8 Default Iterators and Operators](#s2.8-default-iterators-and-operators) - [2.9 Generators](#s2.9-generators) - [2.10 Lambda Functions](#s2.10-lambda-functions) - [2.11 Conditional Expressions](#s2.11-conditional-expressions) - [2.12 Default Argument Values](#s2.12-default-argument-values) - [2.13 Properties](#s2.13-properties) - [2.14 True/False Evaluations](#s2.14-truefalse-evaluations) - [2.16 Lexical Scoping](#s2.16-lexical-scoping) - [2.17 Function and Method Decorators](#s2.17-function-and-method-decorators) - [2.18 Threading](#s2.18-threading) - [2.19 Power Features](#s2.19-power-features) - [2.20 Modern Python: from \_\_future\_\_ imports](#s2.20-modern-python) - [2.21 Type Annotated Code](#s2.21-type-annotated-code) - [3 Python Style Rules](#s3-python-style-rules) - [3.1 Semicolons](#s3.1-semicolons) - [3.2 Line length](#s3.2-line-length) - [3.3 Parentheses](#s3.3-parentheses) - [3.4 Indentation](#s3.4-indentation) - [3.5 Blank Lines](#s3.5-blank-lines) - [3.6 Whitespace](#s3.6-whitespace) - [3.7 Shebang Line](#s3.7-shebang-line) - [3.8 Comments and Docstrings](#s3.8-comments-and-docstrings) - [3.10 Strings](#s3.10-strings) - [3.11 Files, Sockets, and similar Stateful Resources](#s3.11-files-sockets-closeables) - [3.12 TODO Comments](#s3.12-todo-comments) - [3.13 Imports formatting](#s3.13-imports-formatting) - [3.14 Statements](#s3.14-statements) - [3.15 Accessors](#s3.15-accessors) - [3.16 Naming](#s3.16-naming) - [3.17 Main](#s3.17-main) - [3.18 Function length](#s3.18-function-length) - [3.19 Type Annotations](#s3.19-type-annotations) - [4 Parting Words](#4-parting-words)1 Background¶
Python is the main dynamic language used at Google. This style guide is a list of dos and don'ts for Python programs.
Many teams use the Black or Pyink auto-formatter to avoid arguing over formatting.
2 Python Language Rules¶
2.1 Lint¶
Run ruff over your code.
2.1.1 Definition¶
ruff is a tool for finding bugs and style problems in Python source code. It finds problems that are typically caught by a compiler for less dynamic languages like C and C++.
2.1.2 Pros¶
Catches easy-to-miss errors like typos, using-vars-before-assignment, etc.
2.1.3 Cons¶
ruff isn't perfect. To take advantage of it, sometimes we'll need to write around it, suppress its warnings or fix it.
2.1.4 Decision¶
Make sure you run ruff on your code.
Suppress warnings if they are inappropriate so that other issues are not hidden. To suppress warnings, you can set a line-level comment:
If the reason for the suppression is not clear from the comment, add an explanation.
2.2 Imports¶
Use import statements for packages and modules only, not for individual types, classes, or functions.
2.2.1 Definition¶
Reusability mechanism for sharing code from one module to another.
2.2.2 Pros¶
The namespace management convention is simple. The source of each identifier is indicated in a consistent way; x.Obj says that object Obj is defined in module x.
2.2.3 Cons¶
Module names can still collide. Some module names are inconveniently long.
2.2.4 Decision¶
- Use
import xfor importing packages and modules. - Use
from x import ywherexis the package prefix andyis the module name with no prefix. - Use
from x import y as zin any of the following circumstances: - Two modules named
yare to be imported. yconflicts with a top-level name defined in the current module.yis an inconveniently long name.- Use
import y as zonly whenzis a standard abbreviation (e.g.,import numpy as np).
For example the module sound.effects.echo may be imported as follows:
Do not use relative names in imports. Even if the module is in the same package, use the full package name.
2.2.4.1 Exemptions¶
Exemptions from this rule:
Symbols from the following modules are used to support static analysis and type checking:
typingmodulecollections.abcmoduletyping_extensionsmodule
2.3 Packages¶
Import each module using the full pathname location of the module.
2.3.1 Pros¶
Avoids conflicts in module names or incorrect imports due to the module search path not being what the author expected.
2.3.2 Cons¶
Makes it harder to deploy code because you have to replicate the package hierarchy. Not really a problem with modern deployment mechanisms.
2.3.3 Decision¶
All new code should import each module by its full package name.
# Yes:
from slurmq import sessions
from slurmq.ui import widgets
# No:
import sessions # Unclear which sessions module
2.4 Exceptions¶
Exceptions are allowed but must be used carefully.
2.4.1 Definition¶
Exceptions are a means of breaking out of normal control flow to handle errors or other exceptional conditions.
2.4.2 Pros¶
The control flow of normal operation code is not cluttered by error-handling code. It also allows the control flow to skip multiple frames when a certain condition occurs.
2.4.3 Cons¶
May cause the control flow to be confusing. Easy to miss error cases when making library calls.
2.4.4 Decision¶
Exceptions must follow certain conditions:
-
Make use of built-in exception classes when it makes sense. For example, raise a
ValueErrorto indicate a programming mistake like a violated precondition. -
Do not use
assertstatements in place of conditionals or validating preconditions. They must not be critical to the application logic.
# Yes:
def connect_to_next_port(self, minimum: int) -> int:
"""Connects to the next available port."""
if minimum < 1024:
raise ValueError(f'Min. port must be at least 1024, not {minimum}.')
port = self._find_next_open_port(minimum)
if port is None:
raise ConnectionError(
f'Could not connect to service on port {minimum} or higher.')
return port
# No:
def connect_to_next_port(self, minimum: int) -> int:
assert minimum >= 1024, 'Minimum port must be at least 1024.'
port = self._find_next_open_port(minimum)
assert port is not None
return port
-
Libraries or packages may define their own exceptions. When doing so they must inherit from an existing exception class. Exception names should end in
Error. -
Never use catch-all
except:statements, or catchExceptionorStandardError, unless you are re-raising the exception or creating an isolation point. -
Minimize the amount of code in a
try/exceptblock. -
Use the
finallyclause to execute code whether or not an exception is raised.
2.5 Mutable Global State¶
Avoid mutable global state.
2.5.1 Definition¶
Module-level values or class attributes that can get mutated during program execution.
2.5.2 Pros¶
Occasionally useful.
2.5.3 Cons¶
Breaks encapsulation: Such design can make it hard to achieve valid objectives.
Has the potential to change module behavior during the import, because assignments to global variables are done when the module is first imported.
2.5.4 Decision¶
Avoid mutable global state.
In those rare cases where using global state is warranted, mutable global entities should be declared at the module level and made internal by prepending an _ to the name.
Module-level constants are permitted and encouraged. For example:
_MAX_RETRIES = 3 for an internal use constant or
DEFAULT_TIMEOUT = 30 for a public API constant.
2.6 Nested/Local/Inner Classes and Functions¶
Nested local functions or classes are fine when used to close over a local variable. Inner classes are fine.
2.6.1 Definition¶
A class can be defined inside of a method, function, or class. A function can be defined inside a method or function.
2.6.2 Pros¶
Allows definition of utility classes and functions that are only used inside of a very limited scope. Commonly used for implementing decorators.
2.6.3 Cons¶
Nested functions and classes cannot be directly tested. Nesting can make the outer function longer and less readable.
2.6.4 Decision¶
They are fine with some caveats. Avoid nested functions or classes except when closing over a local value other than self or cls. Do not nest a function just to hide it from users of a module. Instead, prefix its name with an _ at the module level.
2.7 Comprehensions & Generator Expressions¶
Okay to use for simple cases.
2.7.1 Definition¶
List, Dict, and Set comprehensions as well as generator expressions provide a concise and efficient way to create container types and iterators.
2.7.2 Pros¶
Simple comprehensions can be clearer and simpler than other dict, list, or set creation techniques.
2.7.3 Cons¶
Complicated comprehensions or generator expressions can be hard to read.
2.7.4 Decision¶
Comprehensions are allowed, however multiple for clauses or filter expressions are not permitted. Optimize for readability, not conciseness.
# Yes:
result = [mapping_expr for value in iterable if filter_expr]
result = [
is_valid(metric={'key': value})
for value in interesting_iterable
if a_longer_filter_expression(value)
]
2.8 Default Iterators and Operators¶
Use default iterators and operators for types that support them, like lists, dictionaries, and files.
2.8.1 Definition¶
Container types, like dictionaries and lists, define default iterators and membership test operators ("in" and "not in").
2.8.2 Pros¶
The default iterators and operators are simple and efficient. They express the operation directly, without extra method calls.
2.8.3 Cons¶
You can't tell the type of objects by reading the method names (unless the variable has type annotations).
2.8.4 Decision¶
Use default iterators and operators for types that support them.
# Yes:
for key in adict: ...
if obj in alist: ...
for line in afile: ...
for k, v in adict.items(): ...
2.9 Generators¶
Use generators as needed.
2.9.1 Definition¶
A generator function returns an iterator that yields a value each time it executes a yield statement.
2.9.2 Pros¶
Simpler code, because the state of local variables and control flow are preserved for each call. A generator uses less memory than a function that creates an entire list of values at once.
2.9.3 Cons¶
Local variables in the generator will not be garbage collected until the generator is either consumed to exhaustion or itself garbage collected.
2.9.4 Decision¶
Fine. Use "Yields:" rather than "Returns:" in the docstring for generator functions.
2.10 Lambda Functions¶
Okay for one-liners. Prefer generator expressions over map() or filter() with a lambda.
2.10.1 Definition¶
Lambdas define anonymous functions in an expression, as opposed to a statement.
2.10.2 Pros¶
Convenient.
2.10.3 Cons¶
Harder to read and debug than local functions. The lack of names means stack traces are more difficult to understand.
2.10.4 Decision¶
Lambdas are allowed. If the code inside the lambda function spans multiple lines or is longer than 60-80 chars, it might be better to define it as a regular nested function.
For common operations like multiplication, use the functions from the operator module instead of lambda functions.
2.11 Conditional Expressions¶
Okay for simple cases.
2.11.1 Definition¶
Conditional expressions (sometimes called a "ternary operator") are mechanisms that provide a shorter syntax for if statements. For example: x = 1 if cond else 2.
2.11.2 Pros¶
Shorter and more convenient than an if statement.
2.11.3 Cons¶
May be harder to read than an if statement.
2.11.4 Decision¶
Okay to use for simple cases. Each portion must fit on one line.
2.12 Default Argument Values¶
Okay in most cases.
2.12.1 Definition¶
You can specify values for variables at the end of a function's parameter list, e.g., def foo(a, b=0):.
2.12.2 Pros¶
Often you have a function that uses lots of default values, but on rare occasions you want to override the defaults.
2.12.3 Cons¶
Default arguments are evaluated once at module load time. This may cause problems if the argument is a mutable object such as a list or a dictionary.
2.12.4 Decision¶
Okay to use with the following caveat:
Do not use mutable objects as default values in the function or method definition.
# Yes:
def foo(a, b=None):
if b is None:
b = []
def foo(a, b: Sequence | None = None):
if b is None:
b = []
def foo(a, b: Sequence = ()): # Empty tuple OK since tuples are immutable.
...
2.13 Properties¶
Properties may be used to control getting or setting attributes that require trivial computations or logic.
2.13.1 Definition¶
A way to wrap method calls for getting and setting an attribute as a standard attribute access.
2.13.2 Pros¶
Allows for an attribute access and assignment API rather than getter and setter method calls.
2.13.3 Cons¶
Can hide side-effects much like operator overloading. Can be confusing for subclasses.
2.13.4 Decision¶
Properties are allowed, but should only be used when necessary and match the expectations of typical attribute access.
Properties should be created with the @property decorator. Manually implementing a property descriptor is considered a power feature.
2.14 True/False Evaluations¶
Use the "implicit" false if at all possible (with a few caveats).
2.14.1 Definition¶
Python evaluates certain values as False when in a boolean context. A quick "rule of thumb" is that all "empty" values are considered false, so 0, None, [], {}, '' all evaluate as false.
2.14.2 Pros¶
Conditions using Python booleans are easier to read and less error-prone.
2.14.3 Cons¶
May look strange to C/C++ developers.
2.14.4 Decision¶
Use the "implicit" false if possible, e.g., if foo: rather than if foo != []:.
- Always use
if foo is None:(oris not None) to check for aNonevalue. - Never compare a boolean variable to
Falseusing==. Useif not x:instead. - For sequences (strings, lists, tuples), use the fact that empty sequences are false.
# Yes:
if not users:
print('no users')
if i % 10 == 0:
self.handle_multiple_of_ten()
def f(x=None):
if x is None:
x = []
# No:
if len(users) == 0:
print('no users')
if not i % 10:
self.handle_multiple_of_ten()
def f(x=None):
x = x or []
2.16 Lexical Scoping¶
Okay to use.
A nested Python function can refer to variables defined in enclosing functions, but cannot assign to them.
def get_adder(summand1: float) -> Callable[[float], float]:
"""Returns a function that adds numbers to a given number."""
def adder(summand2: float) -> float:
return summand1 + summand2
return adder
2.17 Function and Method Decorators¶
Use decorators judiciously when there is a clear advantage. Avoid staticmethod and limit use of classmethod.
2.17.1 Definition¶
Decorators for Functions and Methods (a.k.a "the @ notation").
2.17.2 Pros¶
Elegantly specifies some transformation on a method; the transformation might eliminate some repetitive code, enforce invariants, etc.
2.17.3 Cons¶
Decorators can perform arbitrary operations on a function's arguments or return values, resulting in surprising implicit behavior.
2.17.4 Decision¶
Use decorators judiciously when there is a clear advantage. Decorators should follow the same import and naming guidelines as functions.
Never use staticmethod unless forced to in order to integrate with an API 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.
2.18 Threading¶
Do not rely on the atomicity of built-in types.
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.
2.19 Power Features¶
Avoid these features.
2.19.1 Definition¶
Python is an extremely flexible language and gives you many fancy features such as custom metaclasses, access to bytecode, on-the-fly compilation, dynamic inheritance, object reparenting, import hacks, reflection, modification of system internals, etc.
2.19.2 Pros¶
These are powerful language features. They can make your code more compact.
2.19.3 Cons¶
It's very tempting to use these "cool" features when they're not absolutely necessary. It's harder to read, understand, and debug code that's using unusual features.
2.19.4 Decision¶
Avoid these features in your code.
Standard library modules and classes that internally use these features are okay to use (for example, abc.ABCMeta, dataclasses, and enum).
2.20 Modern Python: from __future__ imports¶
Use of from __future__ import statements is encouraged. It allows a given source file to start using more modern Python syntax features today.
2.21 Type Annotated Code¶
You can annotate Python code with type hints. Type-check the code at build time with a type checking tool like ty.
2.21.1 Definition¶
Type annotations (or "type hints") are for function or method arguments and return values:
2.21.2 Pros¶
Type annotations improve the readability and maintainability of your code.
2.21.3 Cons¶
You will have to keep the type declarations up to date.
2.21.4 Decision¶
You are strongly encouraged to enable Python type analysis when updating code. When adding or modifying public APIs, include type annotations.
3 Python Style Rules¶
3.1 Semicolons¶
Do not terminate your lines with semicolons, and do not use semicolons to put two statements on the same line.
3.2 Line length¶
Maximum line length is 120 characters (configured in pyproject.toml).
Explicit exceptions:
- Long import statements.
- URLs, pathnames, or long flags in comments.
- Long string module-level constants.
Do not use a backslash for explicit line continuation. Instead, make use of Python's implicit line joining inside parentheses, brackets and braces.
# Yes:
if (width == 0 and height == 0 and
color == 'red' and emphasis == 'strong'):
...
# No:
if width == 0 and height == 0 and \
color == 'red' and emphasis == 'strong':
...
3.3 Parentheses¶
Use parentheses sparingly.
It is fine, though not required, to use parentheses around tuples. Do not use them in return statements or conditional statements unless using parentheses for implied line continuation.
3.4 Indentation¶
Indent your code blocks with 4 spaces.
Never use tabs. Implied line continuation should align wrapped elements vertically, or use a hanging 4-space indent.
# Yes: Aligned with opening delimiter.
foo = long_function_name(var_one, var_two,
var_three, var_four)
# Yes: 4-space hanging indent; nothing on first line.
foo = long_function_name(
var_one, var_two, var_three,
var_four)
3.4.1 Trailing commas in sequences of items?¶
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.
3.5 Blank Lines¶
Two blank lines between top-level definitions. One blank line between method definitions and between the docstring of a class and the first method.
3.6 Whitespace¶
Follow standard typographic rules for the use of spaces around punctuation.
- No whitespace inside parentheses, brackets or braces.
- No whitespace before a comma, semicolon, or colon.
- Surround binary operators with a single space on either side.
- Never use spaces around
=when passing keyword arguments, except when a type annotation is present.
# Yes:
spam(ham[1], {'eggs': 2}, [])
def complex(real, imag=0.0): return Magic(r=real, i=imag)
def complex(real, imag: float = 0.0): return Magic(r=real, i=imag)
# No:
spam( ham[ 1 ], { 'eggs': 2 }, [ ] )
def complex(real, imag = 0.0): return Magic(r = real, i = imag)
3.7 Shebang Line¶
Most .py files do not need to start with a #! line. Start the main file of a program with #!/usr/bin/env python3.
3.8 Comments and Docstrings¶
Be sure to use the right style for module, function, method docstrings and inline comments.
3.8.1 Docstrings¶
Python uses docstrings to document code. A docstring is a string that is the first statement in a package, module, class or function. Always use the three-double-quote """ format for docstrings.
3.8.2 Modules¶
Files should start with a docstring describing the contents and usage of the module.
"""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.
Typical usage example:
foo = ClassFoo()
bar = foo.function_bar()
"""
3.8.3 Functions and Methods¶
A docstring is mandatory for every function that has one or more of the following properties:
- being part of the public API
- nontrivial size
- non-obvious logic
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.
Retrieves rows pertaining to the given keys from the Table instance
represented by table_handle.
Args:
table_handle: An open smalltable.Table instance.
keys: A sequence of strings representing the key of each table
row to fetch.
require_all_keys: If True only rows with values set for all keys
will be returned.
Returns:
A dict mapping keys to the corresponding table row data fetched.
Raises:
IOError: An error occurred accessing the smalltable.
"""
3.8.4 Classes¶
Classes should have a docstring below the class definition describing the class. Public attributes should be documented in an Attributes section.
class SampleClass:
"""Summary of class here.
Longer class information...
Attributes:
likes_spam: A boolean indicating if we like SPAM or not.
eggs: An integer count of the eggs we have laid.
"""
def __init__(self, likes_spam: bool = False):
"""Initializes the instance based on spam preference."""
self.likes_spam = likes_spam
self.eggs = 0
3.8.5 Block and Inline Comments¶
The final place to have comments is in tricky parts of the code. Complicated operations get a few lines of comments before the operations commence.
# We use a weighted dictionary search to find out where i is in
# the array. We extrapolate position based on the largest num
# in the array and the array size.
if i & (i-1) == 0: # True if i is 0 or a power of 2.
3.10 Strings¶
Use an f-string, the % operator, or the format method for formatting strings. A single join with + is okay but do not format with +.
# Yes:
x = f'name: {name}; score: {n}'
x = '%s, %s!' % (imperative, expletive)
x = a + b
# No:
x = 'name: ' + name + '; score: ' + str(n)
3.10.1 Logging¶
For logging functions that expect a pattern-string as their first argument: Always call them with a string literal (not an f-string!) as their first argument.
# Yes:
logging.info('Current $PAGER is: %s', os.getenv('PAGER', default=''))
# No:
logging.error(f'Cannot write to home directory, $HOME={homedir!r}')
3.11 Files, Sockets, and similar Stateful Resources¶
Explicitly close files and sockets when done with them.
The preferred way to manage files and similar resources is using the with statement:
3.12 TODO Comments¶
Use TODO comments for code that is temporary, a short-term solution, or good-enough but not perfect.
A TODO comment begins with the word TODO in all caps, a following colon, and a link to a resource that contains the context, ideally a bug reference.
3.13 Imports formatting¶
Imports should be on separate lines (except for typing and collections.abc imports).
# Yes:
from collections.abc import Mapping, Sequence
import os
import sys
from typing import Any, NewType
# No:
import os, sys
Imports should be grouped from most generic to least generic:
- Python future import statements
- Python standard library imports
- Third-party module imports
- Local imports
3.14 Statements¶
Generally only one statement per line.
3.15 Getters and Setters¶
Getter and setter functions should be used when they provide a meaningful role or behavior. If a pair of getters/setters simply read and write an internal attribute, the internal attribute should be made public instead.
3.16 Naming¶
module_name, package_name, ClassName, method_name, ExceptionName, function_name, GLOBAL_CONSTANT_NAME, global_var_name, instance_var_name, function_parameter_name, local_var_name.
3.16.1 Names to Avoid¶
- single character names, except for counters/iterators, exception identifiers, file handles
- dashes (
-) in any package/module name __double_leading_and_trailing_underscore__names- offensive terms
3.16.2 Naming Conventions¶
- Prepending a single underscore (
_) has some support for protecting module variables and functions. - Prepending a double underscore (
__) is discouraged as it impacts readability and testability.
| Type | Public | Internal |
|---|---|---|
| Packages | lower_with_under |
|
| Modules | lower_with_under |
_lower_with_under |
| Classes | CapWords |
_CapWords |
| Exceptions | CapWords |
|
| Functions | lower_with_under() |
_lower_with_under() |
| Constants | CAPS_WITH_UNDER |
_CAPS_WITH_UNDER |
| Variables | lower_with_under |
_lower_with_under |
3.17 Main¶
In Python, pydoc as well as unit tests require modules to be importable. If a file is meant to be used as an executable, its main functionality should be in a main() function.
3.18 Function length¶
Prefer small and focused functions.
We recognize that long functions are sometimes appropriate, so no hard limit is placed on function length. If a function exceeds about 40 lines, think about whether it can be broken up without harming the structure of the program.
3.19 Type Annotations¶
3.19.1 General Rules¶
- Annotating
selforclsis generally not necessary. - Don't feel compelled to annotate the return value of
__init__. - If any other variable or a returned type should not be expressed, use
Any. - At least annotate your public APIs.
3.19.2 Line Breaking¶
After annotating, many function signatures will become "one parameter per line".
3.19.3 Forward Declarations¶
If you need to use a class name that is not yet defined, either use from __future__ import annotations or use a string for the class name.
from __future__ import annotations
class MyClass:
def __init__(self, stack: Sequence[MyClass], item: OtherClass) -> None:
...
class OtherClass:
...
3.19.4 Default Values¶
Use spaces around the = only for arguments that have both a type annotation and a default value.
3.19.5 NoneType¶
If an argument can be None, it has to be declared! Use X | None (recommended in Python 3.10+).
# Yes:
def modern_or_union(a: str | int | None, b: str | None = None) -> str:
...
# No:
def implicit_optional(a: str = None) -> str:
...
3.19.6 Type Aliases¶
You can declare aliases of complex types. The name of an alias should be CapWorded.
from typing import TypeAlias
_LossAndGradient: TypeAlias = tuple[tf.Tensor, tf.Tensor]
ComplexTFMap: TypeAlias = Mapping[str, _LossAndGradient]
3.19.7 Ignoring Types¶
You can disable type checking on a line with the special comment # type: ignore.
3.19.8 Typing Variables¶
If an internal variable has a type that is hard or impossible to infer, specify its type with an annotated assignment:
3.19.9 Tuples vs Lists¶
Typed lists can only contain objects of a single type. Typed tuples can either have a single repeated type or a set number of elements with different types.
3.19.10 Type Variables¶
A type variable, such as TypeVar and ParamSpec, is a common way to use generics.
from collections.abc import Callable
from typing import ParamSpec, TypeVar
_P = ParamSpec("_P")
_T = TypeVar("_T")
def next(l: list[_T]) -> _T:
return l.pop()
3.19.11 String types¶
Use str for string/text data. For code that deals with binary data, use bytes.
3.19.12 Imports For Typing¶
For symbols from the typing or collections.abc modules, always import the symbol itself.
3.19.13 Conditional Imports¶
Use conditional imports only in exceptional cases. Imports that are needed only for type annotations can be placed within an if TYPE_CHECKING: block.
3.19.14 Circular Dependencies¶
Circular dependencies that are caused by typing are code smells. Such code is a good candidate for refactoring.
3.19.15 Generics¶
When annotating, prefer to specify type parameters for generic types; otherwise, the generics' parameters will be assumed to be Any.
# Yes:
def get_names(employee_ids: Sequence[int]) -> Mapping[int, str]:
...
# No:
def get_names(employee_ids: Sequence) -> Mapping:
...
4 Parting Words¶
BE CONSISTENT.
If you're editing code, take a few minutes to look at the code around you and determine its style. If their comments have little boxes of hash marks around them, make your comments have little boxes of hash marks around them too.
The point of having style guidelines is to have a common vocabulary of coding so people can concentrate on what you're saying rather than on how you're saying it. We present global style rules here so people know the vocabulary, but local style is also important.
However, there are limits to consistency. It applies more heavily locally and on choices unspecified by the global style. Consistency should not generally be used as a justification to do things in an old style without considering the benefits of the new style.