2 Python 语言规则#

2 Python Language Rules

2.1 Lint#

2.1 Lint

使用此 pylintrc 在您的代码上运行 pylint

Run pylint over your code using this pylintrc .

2.1.1 定义#

2.1.1 Definition

pylint 是一款用于查找 Python 源代码中的错误和代码风格问题的工具。它可以发现一些通常被 C 和 C++ 等动态性较差的语言的编译器捕获的问题。由于 Python 的动态特性,某些警告可能不正确;但是,虚假警告应该很少出现。

pylint 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++. Because of the dynamic nature of Python, some warnings may be incorrect; however, spurious warnings should be fairly infrequent.

2.1.2 优点#

2.1.2 Pros

捕捉容易错过的错误,如拼写错误、赋值前使用变量等。

Catches easy-to-miss errors like typos, using-vars-before-assignment, etc.

2.1.3 缺点#

2.1.3 Cons

pylint 并不完美。为了充分利用它,有时我们需要绕过它,抑制它的警告,或者修复它。

pylint 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 决策#

2.1.4 Decision

确保对你的代码运行 pylint

如果某些警告不适用,应将其抑制,以避免掩盖其他问题。要抑制警告,可以添加一行级注释:

def do_PUT(self):  # WSGI 名称,因此 pylint: disable=invalid-name
...

每个 pylint 警告都有一个符号名称(如 empty-docstring),Google 特定的警告则以 g- 开头。

如果从符号名称无法清晰看出抑制原因,应添加说明。

以这种方式抑制的好处是,我们可以轻松搜索出这些抑制点并重新评估。

你可以通过以下命令获取所有 pylint 警告列表:

pylint --list-msgs

如需获取某个特定警告的详细信息,可使用:

pylint --help-msg=invalid-name

优先使用 pylint: disable,而非已弃用的旧格式 pylint: disable-msg

对于未使用的参数警告,可以在函数开始处删除该变量来抑制。务必附带注释说明为何删除,使用 “Unused.” 即可。例如:

def viking_cafe_order(spam: str, beans: str, eggs: str | None = None) -> str:
    del beans, eggs  # Unused by vikings.
    return spam + spam + spam

其他常见的抑制方式包括使用 ‘_’ 作为未使用参数的标识符,或为参数名前加上 ‘unused_’ 前缀,或者将它们赋值给 ‘_’。这些方式是允许的,但已不再推荐。它们会破坏基于命名参数调用的代码,并且无法强制确保参数确实未被使用。

Make sure you run pylint 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:

def do_PUT(self):  # WSGI name, so pylint: disable=invalid-name
...

pylint warnings are each identified by symbolic name (empty-docstring) Google-specific warnings start with g-.

If the reason for the suppression is not clear from the symbolic name, add an explanation.

Suppressing in this way has the advantage that we can easily search for suppressions and revisit them.

You can get a list of pylint warnings by doing:

pylint --list-msgs

To get more information on a particular message, use:

pylint --help-msg=invalid-name

Prefer pylint: disable to the deprecated older form pylint: disable-msg.

Unused argument warnings can be suppressed by deleting the variables at the beginning of the function. Always include a comment explaining why you are deleting it. “Unused.” is sufficient. For example:

def viking_cafe_order(spam: str, beans: str, eggs: str | None = None) -> str:
    del beans, eggs  # Unused by vikings.
    return spam + spam + spam

Other common forms of suppressing this warning include using ‘_’ as the identifier for the unused argument or prefixing the argument name with ‘unused_’, or assigning them to ‘_’. These forms are allowed but no longer encouraged. These break callers that pass arguments by name and do not enforce that the arguments are actually unused.

2.2 导入#

2.2 Imports

仅对包和模块使用 import 语句,而不是对单个类型、类或函数使用。

Use import statements for packages and modules only, not for individual types, classes, or functions.

2.2.1 定义#

2.2.1 Definition

从一个模块到另一个模块共享代码的可重用机制。

Reusability mechanism for sharing code from one module to another.

2.2.2 优点#

2.2.2 Pros

命名空间管理约定很简单。每个标识符的来源都以一致的方式指示; x.Obj 表示对象 Obj 在模块 x 中定义。

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 缺点#

2.2.3 Cons

模块名称仍然可能冲突。有些模块名称太长,不方便使用。

Module names can still collide. Some module names are inconveniently long.

2.2.4 决策#

2.2.4 Decision

  • 导入包和模块时使用 import x

  • x 是包前缀而 y 是不带前缀的模块名时,使用 from x import y

  • 在以下任一情形中使用 from x import y as z

    • 需要导入两个名称为 y 的模块。

    • y 与当前模块中定义的顶层名称冲突。

    • y 与公共 API 中常见的参数名称冲突(例如 features)。

    • y 名称过长,不便使用。

    • y 在当前代码上下文中名称过于通用(例如:from storage.file_system import options as fs_options )。

  • 仅当 z 是标准缩写时,才使用 import y as z`(例如:`import numpy as np)。

例如,可以如下方式导入模块 sound.effects.echo

from sound.effects import echo
...
echo.EchoFilter(input, output, delay=0.7, atten=4)

不要在导入中使用相对路径名称。即使模块位于同一包中,也应使用完整的包名称。这有助于避免无意中重复导入某个包。

  • Use import x for importing packages and modules.

  • Use from x import y where x is the package prefix and y is the module name with no prefix.

  • Use from x import y as z in any of the following circumstances: - Two modules named y are to be imported. - y conflicts with a top-level name defined in the current module. - y conflicts with a common parameter name that is part of the public API (e.g., features). - y is an inconveniently long name. - y is too generic in the context of your code (e.g., from storage.file_system import options as fs_options).

  • Use import y as z only when z is a standard abbreviation (e.g., import numpy as np).

For example the module sound.effects.echo may be imported as follows:

from sound.effects import echo
...
echo.EchoFilter(input, output, delay=0.7, atten=4)

Do not use relative names in imports. Even if the module is in the same package, use the full package name. This helps prevent unintentionally importing a package twice.

2.2.4.1 豁免#

2.2.4.1 Exemptions

以下情形可不遵守此规则:

Exemptions from this rule:

2.3 包#

2.3 Packages

使用模块的完整路径名位置导入每个模块。

Import each module using the full pathname location of the module.

2.3.1 优点#

2.3.1 Pros

避免由于模块搜索路径不符合作者预期而导致模块名称冲突或导入错误。使模块查找更加便捷。

Avoids conflicts in module names or incorrect imports due to the module search path not being what the author expected. Makes it easier to find modules.

2.3.2 缺点#

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 决策#

2.3.3 Decision

所有新代码都应通过完整的包名称导入模块。

导入语句应如下所示:

Yes:
# 在代码中通过完整名称引用 absl.flags(较为冗长但清晰)。
import absl.flags
from doctor.who import jodie

_FOO = absl.flags.DEFINE_string(...)
Yes:
# 在代码中仅通过模块名引用 flags(常见做法)。
from absl import flags
from doctor.who import jodie

_FOO = flags.DEFINE_string(...)

(假设该文件位于 doctor/who/ jodie.py 也在同一目录中)

No:
# 无法明确作者希望导入哪个模块,也无法确定实际会导入哪个模块。
# 实际的导入行为依赖于控制 sys.path 的外部因素。
# 作者是想导入哪个 jodie 模块?
import jodie

不应假设主程序所在目录会自动添加到 sys.path 中,尽管在某些环境中确实如此。 因此,代码应默认 import jodie 指的是某个第三方或顶层包 jodie,而非本地的 jodie.py 文件。

All new code should import each module by its full package name.

Imports should be as follows:

Yes:
# Reference absl.flags in code with the complete name (verbose).
import absl.flags
from doctor.who import jodie

_FOO = absl.flags.DEFINE_string(...)
Yes:
# Reference flags in code with just the module name (common).
from absl import flags
from doctor.who import jodie

_FOO = flags.DEFINE_string(...)

(assume this file lives in doctor/who/ where jodie.py also exists)

No:
# Unclear what module the author wanted and what will be imported.  The actual
# import behavior depends on external factors controlling sys.path.
# Which possible jodie module did the author intend to import?
import jodie

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 package named jodie, not a local jodie.py.

2.4 异常#

2.4 Exceptions

允许例外,但必须谨慎使用。

Exceptions are allowed but must be used carefully.

2.4.1 定义#

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 优点#

2.4.2 Pros

正常操作代码的控制流不会被错误处理代码弄得杂乱无章。它还允许控制流在特定条件发生时跳过多个帧,例如,一步即可从 N 个嵌套函数返回,而无需逐一查找错误代码。

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, e.g., returning from N nested functions in one step instead of having to plumb error codes through.

2.4.3 缺点#

2.4.3 Cons

可能导致控制流混乱。调用库时容易遗漏错误情况。

May cause the control flow to be confusing. Easy to miss error cases when making library calls.

2.4.4 决策#

2.4.4 Decision

异常的使用必须遵循以下要求:

  • 在合适的情况下使用内建异常类。例如,当违反前置条件(如函数参数校验)时,抛出 ValueError 以表示程序设计错误。

  • 不应使用 assert 语句替代条件语句或前置条件验证。 assert 语句不能作为应用逻辑的关键部分。判断依据是:移除该 assert 后,代码仍应能正常运行。 assert 条件并不保证一定会被执行,详见 官方文档说明。 在使用 pytest 进行测试时,可以且应该使用 assert 来验证预期行为。例如:

    Yes:
    def connect_to_next_port(self, minimum: int) -> int:
        """连接到下一个可用端口。
    
        参数:
        minimum:大于或等于 1024 的端口号。
    
        返回:
        新的最小端口值。
    
        异常:
        ConnectionError:如果找不到可用端口。
        """
        if minimum < 1024:
            # 注意,这里的 ValueError 没有出现在文档字符串的 "Raises:" 部分,
            # 因为不应对 API 误用行为的特定反应提供保证。
            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.')
        # 程序逻辑并不依赖于这个 assert 的结果。
        assert port >= minimum, (
            f'Unexpected port {port} when minimum was {minimum}.')
        return port
    
    No:
    def connect_to_next_port(self, minimum: int) -> int:
        """连接到下一个可用端口。
    
        参数:
        minimum:大于或等于 1024 的端口号。
    
        返回:
        新的最小端口值。
        """
        assert minimum >= 1024, 'Minimum port must be at least 1024.'
        # 后续代码依赖于上述 assert。
        port = self._find_next_open_port(minimum)
        assert port is not None
        # 返回语句的类型检查依赖于该 assert。
        return port
    
  • 库或包可以定义自定义异常,但必须继承自某个已有的异常类。异常名称应以 Error 结尾,且不应出现重复命名(如 foo.FooError)。

  • 不要使用捕获所有异常的 except: 语句,也不要捕获 ExceptionStandardError,除非你:

    • 要重新抛出该异常,或

    • 要创建程序中的一个“隔离点”,在该位置异常不会被继续传播,而是被记录或忽略,例如为了防止线程崩溃而保护其最外层的代码块。

    Python 在这方面非常宽容, except: 会捕获所有内容,包括拼写错误的名称、sys.exit() 调用、按下 Ctrl+C、单元测试失败等各种你并不想捕获的异常。

  • 尽量减少 try/except 块中包含的代码量。try 块越大,越有可能捕获到你意料之外的异常。在这种情况下,try/except 可能会掩盖真正的错误。

  • 使用 finally 子句来编写无论是否发生异常都需要执行的代码。这在进行清理操作时非常有用,比如关闭文件。

Exceptions must follow certain conditions:

  • Make use of built-in exception classes when it makes sense. For example, raise a ValueError to indicate a programming mistake like a violated precondition, such as may happen when validating function arguments.

  • Do not use assert statements in place of conditionals or validating preconditions. They must not be critical to the application logic. A litmus test would be that the assert could be removed without breaking the code. assert conditionals are not guaranteed https://docs.python.org/3/reference/simple_stmts.html#the-assert-statement to be evaluated. For pytest based tests, assert is okay and expected to verify expectations. For example:

    Yes:
    def connect_to_next_port(self, minimum: int) -> int:
        """Connects to the next available port.
    
        Args:
        minimum: A port value greater or equal to 1024.
    
        Returns:
        The new minimum port.
    
        Raises:
        ConnectionError: If no available port is found.
        """
        if minimum < 1024:
        # Note that this raising of ValueError is not mentioned in the doc
        # string's "Raises:" section because it is not appropriate to
        # guarantee this specific behavioral reaction to API misuse.
          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.')
          # The code does not depend on the result of this assert.
          assert port >= minimum, (
              f'Unexpected port {port} when minimum was {minimum}.')
          return port
    
    No:
    def connect_to_next_port(self, minimum: int) -> int:
        """Connects to the next available port.
    
        Args:
        minimum: A port value greater or equal to 1024.
    
        Returns:
        The new minimum port.
        """
        assert minimum >= 1024, 'Minimum port must be at least 1024.'
        # The following code depends on the previous assert.
        port = self._find_next_open_port(minimum)
        assert port is not None
        # The type checking of the return statement relies on the assert.
        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 and should not introduce repetition (foo.FooError).

  • Never use catch-all except: statements, or catch Exception or StandardError, unless you are

    • re-raising the exception, or

    • creating an isolation point in the program where exceptions are not propagated but are recorded and suppressed instead, such as protecting a thread from crashing by guarding its outermost block.

    Python is very tolerant in this regard and except: will really catch everything including misspelled names, sys.exit() calls, Ctrl+C interrupts, unittest failures and all kinds of other exceptions that you simply don’t want to catch.

  • Minimize the amount of code in a try/except block. The larger the body of the try, the more likely that an exception will be raised by a line of code that you didn’t expect to raise an exception. In those cases, the try/except block hides a real error.

  • Use the finally clause to execute code whether or not an exception is raised in the try block. This is often useful for cleanup, i.e., closing a file.

2.5 可变全局状态#

2.5 Mutable Global State

避免可变的全局状态。

Avoid mutable global state.

2.5.1 定义#

2.5.1 Definition

程序执行期间可能发生变异的模块级值或类属性。

Module-level values or class attributes that can get mutated during program execution.

2.5.2 优点#

2.5.2 Pros

偶尔有用。

Occasionally useful.

2.5.3 缺点#

2.5.3 Cons

  • 打破封装性:这类设计可能会妨碍实现合理的目标。例如,如果使用全局状态来管理数据库连接,那么同时连接两个不同的数据库(例如在迁移期间用于比较差异)将变得困难。类似的问题也很容易出现在全局注册表的使用中。

  • 有可能在模块导入期间改变模块的行为,因为全局变量的赋值是在模块首次导入时执行的。

  • Breaks encapsulation: Such design can make it hard to achieve valid objectives. For example, if global state is used to manage a database connection, then connecting to two different databases at the same time (such as for computing differences during a migration) becomes difficult. Similar problems easily arise with global registries.

  • 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 决策#

2.5.4 Decision

避免使用可变的全局状态。

在极少数确有必要使用全局状态的情况下,应将可变的全局实体声明在模块级别或作为类属性,并通过在名称前加下划线 _ 使其成为内部实现。如有必要,外部对可变全局状态的访问必须通过公共函数或类方法进行。参见下文的 命名规范。请在注释中或注释中链接的文档中说明使用可变全局状态的设计原因。

允许并鼓励使用模块级常量。例如:用于内部用途的常量 _MAX_HOLY_HANDGRENADE_COUNT = 3,或用于公共 API 的常量 SIR_LANCELOTS_FAVORITE_COLOR = “blue”。常量名称必须使用全大写加下划线的格式。参见下文的 命名规范

Avoid mutable global state.

In those rare cases where using global state is warranted, mutable global entities should be declared at the module level or as a class attribute and made internal by prepending an _ to the name. If necessary, external access to mutable global state must be done through public functions or class methods. See Naming below. Please explain the design reasons why mutable global state is being used in a comment or a doc linked to from a comment.

Module-level constants are permitted and encouraged. For example: _MAX_HOLY_HANDGRENADE_COUNT = 3 for an internal use constant or SIR_LANCELOTS_FAVORITE_COLOR = “blue” for a public API constant. Constants must be named using all caps with underscores. See Naming below.

2.6 嵌套/本地/内部类和函数#

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 定义#

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. Nested functions have read-only access to variables defined in enclosing scopes.

2.6.2优点#

2.6.2 Pros

允许定义仅在非常有限的范围内使用的实用程序类和函数。非常符合 ADT 规范。常用于实现装饰器。

Allows definition of utility classes and functions that are only used inside of a very limited scope. Very ADT -y. Commonly used for implementing decorators.

2.6.3 缺点#

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 决策#

2.6.4 Decision

它们没有问题,但有一些注意事项。避免使用嵌套函数或类,除非需要覆盖除 selfcls 之外的本地值。不要仅仅为了向模块用户隐藏函数而嵌套函数。相反,应该在模块级别为其名称添加 _ 前缀,以便测试仍然可以访问它。

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 so that it can still be accessed by tests.

2.7 推导式与生成器表达式#

2.7 Comprehensions & Generator Expressions

可以用于简单的情况。

Okay to use for simple cases.

2.7.1 定义#

2.7.1 Definition

列表、字典和集合推导以及生成器表达式提供了一种简洁有效的方法来创建容器类型和迭代器,而无需使用传统循环、map()filter()lambda

List, Dict, and Set comprehensions as well as generator expressions provide a concise and efficient way to create container types and iterators without resorting to the use of traditional loops, map(), filter(), or lambda.

2.7.2 优点#

2.7.2 Pros

简单的推导式比其他字典、列表或集合的创建技术更清晰、更简洁。生成器表达式可以非常高效,因为它们完全避免了创建列表。

Simple comprehensions can be clearer and simpler than other dict, list, or set creation techniques. Generator expressions can be very efficient, since they avoid the creation of a list entirely.

2.7.3 缺点#

2.7.3 Cons

复杂的理解或生成器表达式可能难以阅读。

Complicated comprehensions or generator expressions can be hard to read.

2.7.4 决策#

2.7.4 Decision

允许使用推导式,但不允许使用多个 for 子句或过滤表达式。优化重点在于可读性,而非简洁性。

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)
    ]

    descriptive_name = [
        transform({'key': key, 'value': value}, color='black')
        for key, value in generate_iterable(some_input)
            if complicated_condition_is_met(key, value)
    ]

    result = []
    for x in range(10):
        for y in range(5):
            if x * y > 10:
                result.append((x, y))

    return {
        x: complicated_transform(x)
        for x in long_generator_function(parameter)
        if x is not None
    }

    return (x**2 for x in range(10))

    unique_names = {user.name for user in users if user is not None}
No:
    result = [(x, y) for x in range(10) for y in range(5) if x * y > 10]

    return (
        (x, y, z)
        for x in range(5)
        for y in range(5)
        if x != y
        for z in range(5)
        if y != z
    )

2.8 默认迭代器和运算符#

2.8 Default Iterators and Operators

对支持它们的类型(如列表、字典和文件)使用默认迭代器和运算符。

Use default iterators and operators for types that support them, like lists, dictionaries, and files.

2.8.1 定义#

2.8.1 Definition

容器类型,如字典和列表,定义默认迭代器和成员资格测试运算符(“in”和“not in”)。

Container types, like dictionaries and lists, define default iterators and membership test operators (“in” and “not in”).

2.8.2 优点#

2.8.2 Pros

默认的迭代器和运算符简单高效。它们直接表达操作,无需额外的方法调用。使用默认运算符的函数是泛型函数。它可以用于任何支持该操作的类型。

The default iterators and operators are simple and efficient. They express the operation directly, without extra method calls. A function that uses default operators is generic. It can be used with any type that supports the operation.

2.8.3 缺点#

2.8.3 Cons

你无法通过读取方法名称来判断对象的类型(除非变量有类型注释)。这也是一个优点。

You can’t tell the type of objects by reading the method names (unless the variable has type annotations). This is also an advantage.

2.8.4 决策#

2.8.4 Decision

对于支持默认迭代器和运算符的类型,例如列表、字典和文件,请使用默认迭代器和运算符。内置类型也定义了迭代器方法。优先使用这些方法,而不是返回列表的方法,但迭代容器时不应对其进行修改。

Use default iterators and operators for types that support them, like lists, dictionaries, and files. The built-in types define iterator methods, too. Prefer these methods to methods that return lists, except that you should not mutate a container while iterating over it.

Yes:  for key in adict: ...
      if obj in alist: ...
      for line in afile: ...
      for k, v in adict.items(): ...
No:   for key in adict.keys(): ...
      for line in afile.readlines(): ...

2.9 生成器#

2.9 Generators

根据需要使用生成器。

Use generators as needed.

2.9.1 定义#

2.9.1 Definition

生成器函数返回一个迭代器,该迭代器每次执行 yield 语句时都会产生一个值。产生一个值后,生成器函数的运行时状态将被暂停,直到需要下一个值为止。

A generator function returns an iterator that yields a value each time it executes a yield statement. After it yields a value, the runtime state of the generator function is suspended until the next value is needed.

2.9.2 优点#

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 缺点#

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 决策#

2.9.4 Decision

好的。在生成器函数的文档字符串中使用“Yields:”而不是“Returns:”。

如果生成器管理的是高开销的资源,请务必强制清理。

一个好的清理方法是用上下文管理器 PEP-0533 包装生成器。

Fine. Use “Yields:” rather than “Returns:” in the docstring for generator functions.

If the generator manages an expensive resource, make sure to force the clean up.

A good way to do the clean up is by wrapping the generator with a context manager PEP-0533 .

2.10 Lambda 函数#

2.10 Lambda Functions

单行代码就行。建议使用生成器表达式,而不是 map()filter(),并使用 lambda

Okay for one-liners. Prefer generator expressions over map() or filter() with a lambda.

2.10.1 定义#

2.10.1 Definition

Lambdas 在表达式中定义匿名函数,而不是语句。

Lambdas define anonymous functions in an expression, as opposed to a statement.

2.10.2 优点#

2.10.2 Pros

方便的。

Convenient.

2.10.3 缺点#

2.10.3 Cons

比本地函数更难阅读和调试。缺少名称意味着堆栈跟踪更难理解。由于函数可能只包含一个表达式,因此表达能力有限。

Harder to read and debug than local functions. The lack of names means stack traces are more difficult to understand. Expressiveness is limited because the function may only contain an expression.

2.10.4 决策#

2.10.4 Decision

允许使用 Lambda 表达式。如果 Lambda 函数内的代码跨越多行或长度超过 60-80 个字符,最好将其定义为常规的 嵌套函数

对于乘法等常见运算,请使用 operator 模块中的函数,而不是 Lambda 函数。例如,建议使用 operator.mul 而不是 lambda x, y: x * y

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. For example, prefer operator.mul to lambda x, y: x * y.

2.11 条件表达式#

2.11 Conditional Expressions

对于简单的情况来说还行。

Okay for simple cases.

2.11.1 定义#

2.11.1 Definition

条件表达式(有时也称为“三元运算符”)是一种为 if 语句提供更简洁语法的机制。例如:x = 1 if cond else 2

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 优点#

2.11.2 Pros

比 if 语句更短、更方便。

Shorter and more convenient than an if statement.

2.11.3 缺点#

2.11.3 Cons

可能比 if 语句更难读。如果表达式很长,可能难以找到条件。

May be harder to read than an if statement. The condition may be difficult to locate if the expression is long.

2.11.4 决策#

2.11.4 Decision

适用于简单情况。每个部分必须放在一行:真值表达式、if 表达式、else 表达式。如果情况更复杂,请使用完整的 if 语句。

Okay to use for simple cases. Each portion must fit on one line: true-expression, if-expression, else-expression. Use a complete if statement when things get more complicated.

Yes:
    one_line = 'yes' if predicate(value) else 'no'
    slightly_split = ('yes' if predicate(value)
                    else 'no, nein, nyet')
    the_longest_ternary_style_that_can_be_done = (
        'yes, true, affirmative, confirmed, correct'
        if predicate(value)
        else 'no, false, negative, nay')
No:
    bad_line_breaking = ('yes' if predicate(value) else
                        'no')
    portion_too_long = ('yes'
                        if some_long_module.some_long_predicate_function(
                            really_long_variable_name)
                        else 'no, false, negative, nay')

2.12 默认参数值#

2.12 Default Argument Values

大多数情况下都可以。

Okay in most cases.

2.12.1 定义#

2.12.1 Definition

您可以在函数参数列表的末尾指定变量的值,例如:def foo(a, b=0):。如果仅使用一个参数调用 foo,则 b 设置为 0。如果使用两个参数调用,则 b 具有第二个参数的值。

You can specify values for variables at the end of a function’s parameter list, e.g., def foo(a, b=0):. If foo is called with only one argument, b is set to 0. If it is called with two arguments, b has the value of the second argument.

2.12.2 优点#

2.12.2 Pros

通常,你会遇到一个使用大量默认值的函数,但在极少数情况下,你会想要覆盖这些默认值。默认参数值提供了一种简单的方法来实现这一点,而无需为这些罕见的例外情况定义大量的函数。由于 Python 不支持重载方法/函数,默认参数是一种“伪造(faking)”重载行为的简单方法。

Often you have a function that uses lots of default values, but on rare occasions you want to override the defaults. Default argument values provide an easy way to do this, without having to define lots of functions for the rare exceptions. As Python does not support overloaded methods/functions, default arguments are an easy way of “faking” the overloading behavior.

2.12.3 缺点#

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. If the function modifies the object (e.g., by appending an item to a list), the default value is modified.

2.12.4 决策#

2.12.4 Decision

可以使用,但需要注意以下事项:

请勿在函数或方法定义中使用可变对象作为默认值。

Yes: def foo(a, b=None):
        if b is None:
            b = []
Yes: def foo(a, b: Sequence | None = None):
        if b is None:
            b = []
Yes: def foo(a, b: Sequence = ()):  # 空元组可以,因为元组是不可变的.
        ...
from absl import flags
_FOO = flags.DEFINE_string(...)

No:  def foo(a, b=[]):
        ...
No:  def foo(a, b=time.time()):  # “b” 是否应该代表该模块的加载时间?
        ...
No:  def foo(a, b=_FOO.value):  # sys.argv 尚未被解析...
        ...
No:  def foo(a, b: Mapping = {}):  # 仍可能传递给未经检查的代码。
        ...

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 = []
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.
        ...
from absl import flags
_FOO = flags.DEFINE_string(...)

No:  def foo(a, b=[]):
        ...
No:  def foo(a, b=time.time()):  # Is `b` supposed to represent when this module was loaded?
        ...
No:  def foo(a, b=_FOO.value):  # sys.argv has not yet been parsed...
        ...
No:  def foo(a, b: Mapping = {}):  # Could still get passed to unchecked code.
        ...

2.13 属性#

2.13 Properties

属性可用于控制需要简单计算或逻辑的属性的获取或设置。属性的实现必须符合常规属性访问的一般期望:简洁、直接且不令人意外。

Properties may be used to control getting or setting attributes that require trivial computations or logic. Property implementations must match the general expectations of regular attribute access: that they are cheap, straightforward, and unsurprising.

2.13.1 定义#

2.13.1 Definition

将获取和设置属性的方法调用包装为标准属性访问的方式。

A way to wrap method calls for getting and setting an attribute as a standard attribute access.

2.13.2 优点#

2.13.2 Pros

  • 允许使用属性访问和赋值 API,而不是 getter 和 setter 方法调用。

  • 可用于将属性设为只读。

  • 允许延迟计算。

  • 当类的内部结构独立于类的使用者而发展时,提供一种维护类的公共接口的方法。

  • Allows for an attribute access and assignment API rather than getter and setter method calls.

  • Can be used to make an attribute read-only.

  • Allows calculations to be lazy.

  • Provides a way to maintain the public interface of a class when the internals evolve independently of class users.

2.13.3 缺点#

2.13.3 Cons

  • 可以像运算符重载一样隐藏副作用。

  • 可能会使子类感到困惑。

  • Can hide side-effects much like operator overloading.

  • Can be confusing for subclasses.

2.13.4 决策#

2.13.4 Decision

允许使用属性,但与运算符重载类似,应仅在必要时使用,并且应符合典型属性访问的预期;否则,请遵循 getters 和 setters 规则。

例如,不允许使用属性简单地同时获取和设置内部属性:因为没有进行任何计算,因此该属性是不必要的(改为将属性公开)。相比之下,允许使用属性来控制属性访问或计算 简单 派生的值:其逻辑简单且不足为奇。

应使用 @property 装饰器 创建属性。手动实现属性描述符被视为一项 强大功能

属性的继承可能不明显。请勿使用属性来实现子类可能想要重写和扩展的计算。

Properties are allowed, but, like operator overloading, should only be used when necessary and match the expectations of typical attribute access; follow the getters 和 setters rules otherwise.

For example, using a property to simply both get and set an internal attribute isn’t allowed: there is no computation occurring, so the property is unnecessary (make the attribute public instead). In comparison, using a property to control attribute access or to calculate a trivially derived value is allowed: the logic is simple and unsurprising.

Properties should be created with the @property decorator Manually implementing a property descriptor is considered a power feature.

Inheritance with properties can be non-obvious. Do not use properties to implement computations a subclass may ever want to override and extend.

2.14 真/假判断#

2.14 True/False Evaluations

如果可能的话,使用“隐式”错误(有一些警告)。

Use the “implicit” false if at all possible (with a few caveats).

2.14.1 定义#

2.14.1 Definition

Python 在布尔上下文中将某些值计算为 False。一个简单的“经验法则”是,所有“空”值都被视为 false,因此 0、None、[]、{}、'' 在布尔上下文中均被计算为 false。

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 in a boolean context.

2.14.2 优点#

2.14.2 Pros

使用 Python 布尔值来设置条件更易于阅读,且不易出错。在大多数情况下,它们的速度也更快。

Conditions using Python booleans are easier to read and less error-prone. In most cases, they’re also faster.

2.14.3 缺点#

2.14.3 Cons

对于 C/C++ 开发人员来说可能看起来很奇怪。

May look strange to C/C++ developers.

2.14.4 决策#

2.14.4 Decision

尽可能使用“隐式”假值判断,例如使用 if foo: 而不是 if foo != []:。不过,也有一些注意事项需要留意:

  • 检查一个值是否为 None 时,应始终使用 if foo is None: (或 is not None )。 例如,在判断一个默认值为 None 的变量或参数是否被赋予了其他值时。其他值可能在布尔上下文中也会被判断为假!

  • 永远不要通过 == 将布尔变量与 False 进行比较,应使用 if not x:。 如果你需要区分 FalseNone,可以链式组合表达式,如 if not x and x is not None:

  • 对于序列(字符串、列表、元组),应利用空序列为假值的事实,因此使用 if seq:if not seq: 比使用 if len(seq):if not len(seq): 更佳。

  • 在处理整数时,隐式假值可能弊大于利(例如,可能会误将 None 当作 0 处理)。你可以将已知为整数(且不是 len() 的结果)的值与整数 0 进行比较。

    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 []
    
  • 注意,'0' (即字符串形式的 0 )在布尔上下文中会被视为真值。

  • 注意,Numpy 数组在隐式布尔上下文中可能会抛出异常。判断 np.array 是否为空时,推荐使用其 .size 属性(例如 if not users.size)。

Use the “implicit” false if possible, e.g., if foo: rather than if foo != []:. There are a few caveats that you should keep in mind though:

  • Always use if foo is None: (or is not None) to check for a None value. E.g., when testing whether a variable or argument that defaults to None was set to some other value. The other value might be a value that’s false in a boolean context!

  • Never compare a boolean variable to False using ==. Use if not x: instead. If you need to distinguish False from None then chain the expressions, such as if not x and x is not None:.

  • For sequences (strings, lists, tuples), use the fact that empty sequences are false, so if seq: and if not seq: are preferable to if len(seq): and if not len(seq): respectively.

  • When handling integers, implicit false may involve more risk than benefit (i.e., accidentally handling None as 0). You may compare a value which is known to be an integer (and is not the result of len()) against the integer 0.

    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 []
    
  • Note that '0' (i.e., 0 as string) evaluates to true.

  • Note that Numpy arrays may raise an exception in an implicit boolean context. Prefer the .size attribute when testing emptiness of a np.array (e.g. if not users.size).

2.16 词法作用域#

2.16 Lexical Scoping

可以用。

Okay to use.

2.16.1 定义#

2.16.1 Definition

嵌套的 Python 函数可以引用封闭函数中定义的变量,但不能赋值给它们。变量绑定使用词法作用域进行解析,即基于静态程序文本。在代码块中对名称的任何赋值都会导致 Python 将所有对该名称的引用视为局部变量,即使使用先于赋值。如果出现全局声明,则该名称将被视为全局变量。

此功能的一个使用示例如下:

def get_adder(summand1: float) -> Callable[[float], float]:
    """返回将数字添加到给定数字的函数."""
    def adder(summand2: float) -> float:
        return summand1 + summand2

    return adder

A nested Python function can refer to variables defined in enclosing functions, but cannot assign to them. Variable bindings are resolved using lexical scoping, that is, based on the static program text. Any assignment to a name in a block will cause Python to treat all references to that name as a local variable, even if the use precedes the assignment. If a global declaration occurs, the name is treated as a global variable.

An example of the use of this feature is:

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.16.2 优点#

2.16.2 Pros

通常会导致代码更清晰、更优雅。尤其适合经验丰富的 Lisp 和 Scheme(以及 Haskell、ML 等等)程序员。

Often results in clearer, more elegant code. Especially comforting to experienced Lisp and Scheme (and Haskell and ML and …) programmers.

2.16.3 缺点#

2.16.3 Cons

可能会导致令人困惑的错误,例如基于 PEP-0227 的这个示例:

i = 4
def foo(x: Iterable[int]):
    def bar():
        print(i, end='')
    # ...
    # 这里有一堆代码
    # ...
    for i in x:  # 阿哈, i *is* local to foo, so this is what bar sees
        print(i, end='')
    bar()

So foo([1, 2, 3]) will print 1 2 3 3, not 1 2 3 4.

Can lead to confusing bugs, such as this example based on PEP-0227:

i = 4
def foo(x: Iterable[int]):
    def bar():
        print(i, end='')
    # ...
    # A bunch of code here
    # ...
    for i in x:  # Ah, i *is* local to foo, so this is what bar sees
        print(i, end='')
    bar()

So foo([1, 2, 3]) will print 1 2 3 3, not 1 2 3 4.

2.16.4 决策#

2.16.4 Decision

可以用。

Okay to use.

2.17 函数和方法装饰器#

2.17 Function and Method Decorators

当装饰器有明显优势时,请谨慎使用。避免使用 staticmethod 并限制使用 classmethod

Use decorators judiciously when there is a clear advantage. Avoid staticmethod and limit use of classmethod.

2.17.1 定义#

2.17.1 Definition

函数和方法的装饰器 <https://docs.python.org/3/glossary.html#term-decorator>`_(又称“ :code:`@ 符号 ”)。一个常见的装饰器是 @property,用于将普通方法转换为动态计算的属性。然而,装饰器语法也允许用户自定义装饰器。具体来说,对于某个函数 my_decorator,如下所示:

class C:
    @my_decorator
    def method(self):
        # method body ...

相当于:

class C:
    def method(self):
        # method body ...

    method = my_decorator(method)

Decorators for Functions and Methods (a.k.a “the @ notation”). One common decorator is @property, used for converting ordinary methods into dynamically computed attributes. However, the decorator syntax allows for user-defined decorators as well. Specifically, for some function my_decorator, this:

class C:
    @my_decorator
    def method(self):
        # method body ...

is equivalent to:

class C:
    def method(self):
        # method body ...

    method = my_decorator(method)

2.17.2 优点#

2.17.2 Pros

优雅地指定方法上的一些转换;转换可能会消除一些重复的代码,强制执行不变量等。

Elegantly specifies some transformation on a method; the transformation might eliminate some repetitive code, enforce invariants, etc.

2.17.3 缺点#

2.17.3 Cons

装饰器可以对函数的参数或返回值执行任意操作,从而导致令人意外的隐式行为。此外,装饰器在对象定义时执行。对于模块级对象(类、模块函数等),这发生在导入时。装饰器代码中的错误几乎无法恢复。

Decorators can perform arbitrary operations on a function’s arguments or return values, resulting in surprising implicit behavior. Additionally, decorators execute at object definition time. For module-level objects (classes, module functions, …) this happens at import time. Failures in decorator code are pretty much impossible to recover from.

2.17.4 决策#

2.17.4 Decision

当装饰器有明显优势时,请谨慎使用。装饰器应遵循与函数相同的导入和命名准则。装饰器文档字符串应清晰地声明该函数是一个装饰器。请为装饰器编写单元测试。

避免在装饰器本身中引入外部依赖(例如,不要依赖文件、套接字、数据库连接等),因为这些依赖在装饰器运行时可能不可用(在导入时,可能是从 pydoc 或其他工具导入)。使用有效参数调用的装饰器应(尽可能)保证在所有情况下都能成功调用。

装饰器是“顶层代码”的一种特殊情况 - 更多讨论请参阅 main

除非为了与现有库中定义的 API 集成而被迫使用 staticmethod,否则切勿使用 staticmethod。请改为编写模块级函数。

仅在编写命名构造函数或修改必要的全局状态(例如进程范围的缓存)的特定于类的例程时使用 classmethod

Use decorators judiciously when there is a clear advantage. Decorators should follow the same import and naming guidelines as functions. A decorator docstring should clearly state that the function is a decorator. Write unit tests for decorators.

Avoid external dependencies in the decorator itself (e.g. don’t rely on files, sockets, database connections, etc.), since they might not be available when the 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 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.

Use classmethod only when writing a named constructor, or a class-specific routine that modifies necessary global state such as a process-wide cache.

2.18 线程#

2.18 Threading

不要依赖内置类型的原子性。

虽然 Python 的内置数据类型(例如字典)似乎具有原子操作,但在某些情况下它们并非原子操作(例如,如果 __hash____eq__ 被实现为 Python 方法),则不应依赖它们的原子性。也不应该依赖原子变量赋值(因为这反过来又依赖于字典)。

使用 queue 模块的 Queue 数据类型作为线程间数据通信的首选方式。否则,请使用 threading 模块及其锁定原语。优先使用条件变量和 threading.Condition,而不是使用低级锁。

Do not rely on the atomicity of built-in types.

While Python’s built-in data types such as dictionaries appear to have atomic operations, there are corner cases where they aren’t atomic (e.g. if __hash__ 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 primitives. Prefer condition variables and threading.Condition instead of using lower-level locks.

2.19 强大的功能#

2.19 Power Features

避免这些功能。

Avoid these features.

2.19.1 定义#

2.19.1 Definition

Python 是一种非常灵活的语言,它为您提供了许多奇特的功能,例如自定义元类、访问字节码、动态编译、动态继承、对象重新父级、导入黑客、反射(例如 getattr() 的一些用途)、修改系统内部、 __del__ 方法实现自定义清理等。

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 (e.g. some uses of getattr()), modification of system internals, __del__ methods implementing customized cleanup, etc.

2.19.2 优点#

2.19.2 Pros

这些都是强大的语言特性。它们可以让你的代码更加紧凑。

These are powerful language features. They can make your code more compact.

2.19.3 缺点#

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 underneath. It doesn’t seem that way at first (to the original author), but when revisiting the code, it tends to be more difficult than code that is longer but is straightforward.

2.19.4 决策#

2.19.4 Decision

避免在代码中使用这些功能。

内部使用这些功能的标准库模块和类是可以使用的(例如:abc.ABCMetadataclassesenum)。

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 现代 Python:从 __future__ 导入#

2.20 Modern Python: from __future__ imports

新的语言版本语义变化可能会受到特殊未来导入的控制,以便在早期运行时按文件启用它们。

New language version semantic changes may be gated behind a special future import to enable them on a per-file basis within earlier runtimes.

2.20.1 定义#

2.20.1 Definition

能够通过 from __future__ import 语句启用一些更现代的功能,从而可以提前使用预期的未来 Python 版本中的功能。

Being able to turn on some of the more modern features via from __future__ import statements allows early use of features from expected future Python versions.

2.20.2 优点#

2.20.2 Pros

事实证明,这可以使运行时版本升级更加顺畅,因为可以逐个文件进行更改,同时声明兼容性并防止这些文件中出现回归问题。现代代码更易于维护,因为它不太可能积累技术债务,而这些债务在未来的运行时升级中会造成问题。

This has proven to make runtime version upgrades smoother as changes can be made on a per-file basis while declaring compatibility and preventing regressions within those files. Modern code is more maintainable as it is less likely to accumulate technical debt that will be problematic during future runtime upgrades.

2.20.3 缺点#

2.20.3 Cons

此类代码可能无法在引入所需的 Future 语句之前的旧解释器版本上运行。这种需求在支持极其多样化环境的项目中更为常见。

Such code may not work on very old interpreter versions prior to the introduction of the needed future statement. The need for this is more common in projects supporting an extremely wide variety of environments.

2.20.4 决策#

2.20.4 Decision

__future__ 导入#

from __future__ imports

鼓励使用 from __future__ import 语句。它允许给定的源文件立即使用更现代的 Python 语法特性。当您不再需要运行隐藏在 __future__ 导入语句后面的版本时,请随时删除这些代码行。

在可能在 3.5 版(而非 >= 3.7 版)上执行的代码中,导入:

from __future__ import generator_stop

更多信息请阅读 Python Future 语句定义 文档。

除非您确信代码只会在足够现代化的环境中运行,否则请勿删除这些导入语句。即使您目前没有在代码中使用特定 Future 导入语句启用的功能,将其保留在文件中也可以防止以后对代码的修改无意中依赖于旧的行为。

请根据需要使用其他 from __future__ 导入语句。

Use of from __future__ import statements is encouraged. It allows a given source file to start using more modern Python syntax features today. Once you no longer need to run on a version where the features are hidden behind a __future__ import, feel free to remove those lines.

In code that may execute on versions as old as 3.5 rather than >= 3.7, import:

from __future__ import generator_stop

For more information read the Python future statement definitions documentation.

Please don’t remove these imports until you are confident the code is only ever used in a sufficiently modern environment. Even if you do not currently use the feature a specific future import enables in your code today, keeping it in place in the file prevents later modifications of the code from inadvertently depending on the older behavior.

Use other from __future__ import statements as you see fit.

2.21 带类型注释的代码#

2.21 Type Annotated Code

您可以使用 类型提示 为 Python 代码添加注释。在构建时,请使用 pytype 等类型检查工具对代码进行类型检查。在大多数情况下,如果可行,类型注释都包含在源文件中。对于第三方或扩展模块,注释可以包含在 stub .pyi 文件 中。

译注

mypy 是一个python官方支持的静态类型检查工具, 可以较好的结合各类IDE来检查你写的代码的静态类型。

You can annotate Python code with type hints . Type-check the code at build time with a type checking tool like pytype . In most cases, when feasible, type annotations are in source files. For third-party or extension modules, annotations can be in stub .pyi files .

2.21.1 定义#

2.21.1 Definition

类型注释(或“类型提示”)用于函数或方法的参数和返回值:

def func(a: int) -> list[int]:

您还可以使用类似的语法声明变量的类型:

a: SomeType = some_func()

Type annotations (or “type hints”) are for function or method arguments and return values:

def func(a: int) -> list[int]:

You can also declare the type of a variable using similar syntax:

a: SomeType = some_func()

2.21.2 优点#

2.21.2 Pros

类型注解可以提升代码的可读性和可维护性。类型检查器会将许多运行时错误转换为构建时错误,从而降低您使用 强大功能 的能力。

Type annotations improve the readability and maintainability of your code. The type checker will convert many runtime errors to build-time errors, and reduce your ability to use Power Features.

2.21.3 缺点#

2.21.3 Cons

您必须保持类型声明的更新。您可能会看到一些您认为是有效代码的类型错误。使用 类型检查器 可能会降低您使用 强大功能 的能力。

You will have to keep the type declarations up to date. You might see type errors that you think are valid code. Use of a type checker may reduce your ability to use Power Features .

2.21.4 决策#

2.21.4 Decision

强烈建议您在更新代码时启用 Python 类型分析。添加或修改公共 API 时,请在构建系统中添加类型注解并启用通过 pytype 进行检查的功能。由于静态分析对于 Python 来说相对较新,我们承认一些不良副作用(例如错误的类型推断)可能会阻碍某些项目的采用。在这种情况下,我们鼓励作者在 BUILD 文件或代码中添加 TODO 注释或指向 bug 的链接,以描述当前阻碍类型注解采用的问题。

You are strongly encouraged to enable Python type analysis when updating code. When adding or modifying public APIs, include type annotations and enable checking via pytype in the build system. As static analysis is relatively new to Python, we acknowledge that undesired side-effects (such as wrongly inferred types) may prevent adoption by some projects. In those situations, authors are encouraged to add a comment with a TODO or link to a bug describing the issue(s) currently preventing type annotation adoption in the BUILD file or in the code itself as appropriate.