Skip to Content
Python2 Python 语言规则

2 Python 语言规则

2.1 Lint

使用此 pylintrc  对你的代码运行 pylint

2.1.1 定义

pylint 是一个用于查找 Python 源代码中的错误和风格问题的工具。它能发现通常由 C 和 C++ 等不太动态的语言的编译器捕获的问题。由于 Python 的动态特性,某些警告可能不正确;但是,误报应该相当少见。

2.1.2 优点

能捕获容易遗漏的错误,如拼写错误、在赋值前使用变量等。

2.1.3 缺点

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

2.1.4 决定

确保对你的代码运行 pylint

如果警告不合适,则抑制它们,这样其他问题就不会被隐藏。要抑制警告,可以设置行级注释:

def do_PUT(self): # WSGI name, so 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_’ 前缀,或将它们赋值给 ‘_’。这些形式是允许的,但不再推荐。这些方式会破坏按名称传递参数的调用者,而且不能强制保证参数确实未被使用。

2.2 导入(Imports)

仅对包和模块使用 import 语句,不要对单独的类型、类或函数使用。

2.2.1 定义

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

2.2.2 优点

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

2.2.3 缺点

模块名仍然可能冲突。某些模块名不方便地过长。

2.2.4 决定

  • 使用 import x 导入包和模块。
  • 使用 from x import y,其中 x 是包前缀,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)

不要在导入中使用相对名称。即使模块在同一个包中,也要使用完整的包名。这有助于防止无意中两次导入一个包。

2.2.4.1 豁免

此规则的豁免情况:

2.3 包(Packages)

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

2.3.1 优点

避免模块名冲突或由于模块搜索路径不符合作者预期而导致的错误导入。更容易找到模块。

2.3.2 缺点

使部署代码变得更难,因为你必须复制包层次结构。但使用现代部署机制后这并不是真正的问题。

2.3.3 决定

所有新代码都应使用完整包名导入每个模块。

导入应如下所示:

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

2.4 异常(Exceptions)

异常是允许的,但必须谨慎使用。

2.4.1 定义

异常是一种跳出正常控制流来处理错误或其他异常情况的手段。

2.4.2 优点

正常操作代码的控制流不会被错误处理代码所打乱。它还允许在某些条件发生时跳过多个栈帧,例如一步从 N 个嵌套函数中返回,而不必逐层传递错误码。

2.4.3 缺点

可能导致控制流令人困惑。调用库函数时容易遗漏错误情况。

2.4.4 决定

异常必须遵循以下条件:

  • 在合理的情况下使用内置异常类。例如,抛出 ValueError 来指示编程错误(如违反前置条件),比如在验证函数参数时可能发生的情况。

  • 不要使用 assert 语句代替条件判断或前置条件验证。它们不能对应用逻辑至关重要。一个试金石是:删除 assert 后代码不应被破坏。assert 条件不保证 会被求值。对于基于 pytest  的测试,使用 assert 是可以的,也是预期的做法。例如:

    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
  • 库或包可以定义自己的异常。在这样做时,它们必须继承自现有的异常类。异常名称应以 Error 结尾,并且不应引入重复(如 foo.FooError)。

  • 永远不要使用全捕获的 except: 语句,或捕获 ExceptionStandardError,除非你是

    • 重新抛出该异常,或
    • 在程序中创建一个隔离点,在该点异常不会被传播而是被记录和抑制,例如通过保护线程的最外层代码块来防止线程崩溃。

    Python 在这方面非常宽容,except: 会真正捕获一切,包括拼错的名称、sys.exit() 调用、Ctrl+C 中断、单元测试失败以及所有其他你根本不想捕获的异常。

  • 最小化 try/except 代码块中的代码量。try 的主体越大,就越可能有你没预料到会抛出异常的代码行抛出了异常。在这些情况下,try/except 代码块隐藏了真正的错误。

  • 使用 finally 子句来执行不论 try 块中是否抛出异常都需要执行的代码。这在清理时经常很有用,例如关闭文件。

2.5 可变全局状态(Mutable Global State)

避免可变全局状态。

2.5.1 定义

模块级别的值或类属性,可以在程序执行期间被修改。

2.5.2 优点

偶尔有用。

2.5.3 缺点

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

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

2.5.4 决定

避免可变全局状态。

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

模块级常量是允许并鼓励的。例如:_MAX_HOLY_HANDGRENADE_COUNT = 3 用于内部使用常量,或 SIR_LANCELOTS_FAVORITE_COLOR = "blue" 用于公共 API 常量。常量必须使用全大写加下划线命名。请参阅下面的命名

2.6 嵌套/局部/内部类和函数

当用于闭包局部变量时,嵌套局部函数或类是可以的。内部类也是可以的。

2.6.1 定义

类可以在方法、函数或类内部定义。函数可以在方法或函数内部定义。嵌套函数对封闭作用域中定义的变量具有只读访问权限。

2.6.2 优点

允许定义仅在非常有限的作用域内使用的工具类和函数。非常符合抽象数据类型(ADT) 风格。常用于实现装饰器(Decorator)。

2.6.3 缺点

嵌套函数和类无法直接测试。嵌套会使外部函数更长且更难阅读。

2.6.4 决定

它们在某些注意事项下是可以的。避免使用嵌套函数或类,除非需要闭包封闭作用域中 selfcls 以外的局部值。不要仅仅为了对模块用户隐藏函数而嵌套它。相反,在模块级别为其名称加 _ 前缀,这样测试仍然可以访问它。

2.7 推导式(Comprehensions)和生成器表达式(Generator Expressions)

简单情况下可以使用。

2.7.1 定义

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

2.7.2 优点

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

2.7.3 缺点

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

2.7.4 决定

推导式是允许的,但不允许使用多个 for 子句或过滤表达式。以可读性为优化目标,而非简洁性。

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.1 定义

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

2.8.2 优点

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

2.8.3 缺点

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

2.8.4 决定

对于支持默认迭代器和运算符的类型(如列表、字典和文件),使用它们。内置类型也定义了迭代器方法。优先使用这些方法而非返回列表的方法,但不应在迭代容器的同时修改容器。

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 生成器(Generators)

按需使用生成器。

2.9.1 定义

生成器函数返回一个迭代器,每次执行 yield 语句时产生一个值。在产生一个值之后,生成器函数的运行时状态会被挂起,直到需要下一个值。

2.9.2 优点

代码更简单,因为每次调用时局部变量和控制流的状态都会被保留。生成器比一次创建整个值列表的函数使用更少的内存。

2.9.3 缺点

生成器中的局部变量不会被垃圾回收,直到生成器被耗尽或自身被垃圾回收。

2.9.4 决定

可以使用。在生成器函数的文档字符串中使用 “Yields:” 而非 “Returns:”。

如果生成器管理昂贵的资源,确保强制进行清理。

清理的好方法是用上下文管理器(Context Manager)包装生成器,参见 PEP-0533 

2.10 Lambda 函数

单行代码可以使用。优先使用生成器表达式而非带 lambdamap()filter()

2.10.1 定义

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

2.10.2 优点

方便。

2.10.3 缺点

比局部函数更难阅读和调试。缺少名称意味着堆栈追踪更难理解。表达能力有限,因为函数只能包含一个表达式。

2.10.4 决定

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

对于乘法等常见操作,使用 operator 模块中的函数而非 lambda 函数。例如,优先使用 operator.mul 而非 lambda x, y: x * y

2.11 条件表达式(Conditional Expressions)

简单情况下可以使用。

2.11.1 定义

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

2.11.2 优点

比 if 语句更短、更方便。

2.11.3 缺点

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

2.11.4 决定

简单情况下可以使用。每个部分必须放在一行内:true 表达式、if 表达式、else 表达式。当情况变得更复杂时,使用完整的 if 语句。

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 默认参数值(Default Argument Values)

大多数情况下可以使用。

2.12.1 定义

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

2.12.2 优点

通常你有一个使用大量默认值的函数,但在极少数情况下你想覆盖默认值。默认参数值提供了一种简单的方法来做到这一点,而无需为罕见的例外定义大量函数。由于 Python 不支持重载方法/函数,默认参数是一种简单的”模拟”重载行为的方式。

2.12.3 缺点

默认参数在模块加载时被求值一次。如果参数是可变对象(如列表或字典),这可能会导致问题。如果函数修改了该对象(例如向列表追加元素),默认值就会被修改。

2.12.4 决定

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

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

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 = {}): # 仍然可能被传递给未检查的代码。 ...

2.13 属性(Properties)

属性可以用于控制需要简单计算或逻辑的属性获取或设置。属性的实现必须符合常规属性访问的一般预期:即它们是低开销的、直观的和不令人意外的。

2.13.1 定义

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

2.13.2 优点

  • 允许使用属性访问和赋值 API,而非 getter 和 setter 方法调用。
  • 可用于使属性只读。
  • 允许计算延迟执行。
  • 提供一种方式来维护类的公共接口,同时内部实现可以独立于类用户进行演变。

2.13.3 缺点

  • 可以像运算符重载一样隐藏副作用。
  • 对子类可能造成困惑。

2.13.4 决定

属性是允许的,但和运算符重载一样,只应在必要时使用,并且要符合典型属性访问的预期;否则请遵循 getter 和 setter 规则。

例如,使用属性来简单地获取和设置内部属性是不允许的:没有发生计算,所以属性是不必要的(改为将属性设为公共的)。相比之下,使用属性来控制属性访问或计算一个简单的派生值是允许的:逻辑简单且不令人意外。

属性应使用 @property 装饰器创建。手动实现属性描述符被视为高级特性

属性的继承可能不够直观。不要使用属性来实现子类可能需要覆盖和扩展的计算。

2.14 True/False 求值

如果可能,使用”隐式”假值(有一些注意事项)。

2.14.1 定义

Python 在布尔上下文中将某些值求值为 False。一个快速的”经验法则”是所有”空”值都被认为是假的,所以 0, None, [], {}, '' 在布尔上下文中都求值为假。

2.14.2 优点

使用 Python 布尔值的条件更容易阅读,更不容易出错。在大多数情况下,它们也更快。

2.14.3 缺点

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

2.14.4 决定

如果可能,使用”隐式”假值,例如用 if foo: 而非 if foo != []:。不过有一些注意事项你应该记住:

  • 始终使用 if foo is None:(或 is not None)来检查 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)求值为 true。

  • 注意 Numpy 数组在隐式布尔上下文中可能引发异常。测试 np.array 是否为空时,优先使用 .size 属性(例如 if not users.size)。

2.16 词法作用域(Lexical Scoping)

可以使用。

2.16.1 定义

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

以下是此特性使用的一个示例:

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

通常产生更清晰、更优雅的代码。尤其令有经验的 Lisp 和 Scheme(以及 Haskell 和 ML 等)程序员感到舒适。

2.16.3 缺点

可能导致令人困惑的 bug,例如这个基于 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()

所以 foo([1, 2, 3]) 会打印 1 2 3 3,而不是 1 2 3 4

2.16.4 决定

可以使用。

2.17 函数和方法装饰器(Decorators)

在有明确优势时审慎使用装饰器。避免使用 staticmethod,限制 classmethod 的使用。

2.17.1 定义

函数和方法的装饰器 (又称”@ 标记法”)。一个常见的装饰器是 @property,用于将普通方法转换为动态计算的属性。然而,装饰器语法也允许用户自定义装饰器。具体来说,对于某个函数 my_decorator,以下代码:

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

等价于:

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

2.17.2 优点

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

2.17.3 缺点

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

2.17.4 决定

在有明确优势时审慎使用装饰器。装饰器应遵循与函数相同的导入和命名准则。装饰器的文档字符串应明确说明该函数是一个装饰器。为装饰器编写单元测试。

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

装饰器是”顶层代码”的一个特殊情况——参见 main 了解更多讨论。

永远不要使用 staticmethod,除非被迫与现有库中定义的 API 集成。改为编写模块级函数。

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

2.18 线程(Threading)

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

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

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

2.19 高级特性(Power Features)

避免使用这些特性。

2.19.1 定义

Python 是一种极其灵活的语言,提供了许多高级特性,如自定义元类(Metaclass)、访问字节码、动态编译、动态继承、对象重新绑定父类、导入黑技术、反射(Reflection)(例如 getattr() 的某些用法)、修改系统内部、实现自定义清理的 __del__ 方法等。

2.19.2 优点

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

2.19.3 缺点

当这些”酷”特性不是绝对必要时,使用它们是非常诱人的。使用不寻常特性的代码更难阅读、理解和调试。起初可能看起来不是这样(对原始作者而言),但重新审视代码时,它往往比更长但更直观的代码更难理解。

2.19.4 决定

在你的代码中避免使用这些特性。

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

2.20 现代 Python: from __future__ imports

新的语言版本语义变更可以通过特殊的 future 导入来在每个文件的基础上在早期运行时中启用。

2.20.1 定义

能够通过 from __future__ import 语句启用一些更现代的特性,允许提前使用预期的未来 Python 版本的特性。

2.20.2 优点

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

2.20.3 缺点

这样的代码可能无法在引入所需 future 语句之前的非常旧的解释器版本上工作。在支持极广泛环境的项目中,这个需求更为常见。

2.20.4 决定

from __future__ imports

鼓励使用 from __future__ import 语句。它允许给定的源文件今天就开始使用更现代的 Python 语法特性。一旦你不再需要在隐藏该特性于 __future__ 导入之后的版本上运行,可以随意删除那些行。

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

from __future__ import generator_stop

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

在你确信代码仅在足够现代的环境中使用之前,请不要删除这些导入。即使你当前在代码中没有使用特定 future 导入启用的特性,将其保留在文件中可以防止后续的代码修改无意中依赖旧的行为。

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

2.21 类型注解代码(Type Annotated Code)

你可以使用类型提示(Type Hints) 注解 Python 代码。在构建时使用类型检查工具如 pytype  对代码进行类型检查。在大多数情况下,如果可行,类型注解应直接写在源文件中。对于第三方或扩展模块,注解可以放在 stub .pyi 文件中。

2.21.1 定义

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

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

你也可以使用类似的语法声明变量的类型:

a: SomeType = some_func()

2.21.2 优点

类型注解提高了代码的可读性和可维护性。类型检查器会将许多运行时错误转换为构建时错误,并减少你使用高级特性的可能性。

2.21.3 缺点

你必须保持类型声明的更新。你可能会看到你认为是有效代码的类型错误。使用类型检查器 可能会降低你使用高级特性的能力。

2.21.4 决定

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

Last updated on