Google Python 风格指南

1 背景

Python 是 Google 使用的主要动态语言。 这份风格指南是 Python 程序的编程准则

为帮助你正确设置代码格式,我们创建了一个针对 Vim 的配置文件 对于 Emacs,默认设置应该就可以。

许多团队使用 yapf 自动格式化,以避免在格式上争论。

2 Python 语言规则

2.1 Lint

在你的代码上运行 pylint

2.1.1 定义

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

2.1.2 优点

捕获容易忽视的错误,如拼写错误、未赋值的变量等。

2.1.3 缺点

pylint并不完美。 要利用它,有时我们需要:(a)围绕它来写代码(b)抑制它的警告(c)对它进行改进。

2.1.4 结论

确保在你的代码上运行pylint

抑制不适当的警告,以便不隐藏其它问题。 要抑制警告,可以设置行级注释:

dict = 'something awful'  # Bad Idea... pylint: disable=redefined-builtin

pylint的每个警告都有名称标识(empty-docstring)。与 Google 相关的警告则以g-开头。

如果根据警告的名称推测抑制它的原因不是很明确,请添加说明。

以这种方式抑制警告有个优点是我们可以轻松地查找抑制的警告并重新回顾它们。

你可以通过执行以下操作获取pylint警告列表:

pylint --list-msgs

要获取有关特定警告消息的详细信息,请使用:

pylint --help-msg=C6409

相比已弃用的旧式pylint: disable-msg,推荐使用pylint: disable

可以通过在函数的开头删除变量来抑制未使用的参数警告。 始终包含注释,解释删除它的原因。 "未使用"就足够了。 例如:

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

抑制这个警告的其它常见形式包括:使用“_”作为未使用参数的标识符、在参数名称前加上“unused_”或将其赋值给“_”。 这些形式虽然是允许的,但不再鼓励。 前两种形式使得调用者不能按名称传递参数,而最后一种形式没有强制参数实际上不使用。

2.2 导入

仅对包和模块使用import语句,而不是单个类或函数。 注意,从typing 模块的导入是一个明确的例外。

2.2.1 定义

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

2.2.2 优点

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

2.2.3 缺点

模块名称仍可能发生冲突。 某些模块名称很长。

2.2.4 结论

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

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

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

typing 模块six.moves 模块导入不受此规则限制。

2.3 包

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

2.3.1 优点

避免模块名称冲突或由于模块搜索路径不是作者期望的那样而导入不正确。 使查找模块更加容易。

2.3.2 缺点

由于必须复制包的层次结构,因此部署代码变得困难一些。 有了现代部署机制,实际上不是问题。

2.3.3 结论

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

导入应如下所示:

正确:

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

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

FLAGS = flags.FLAGS

错误:(假设下面的文件位于doctor/who/jodie.py同样位于这里)

# 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

不应假定主二进制文件所在的目录位于sys.path中,尽管在某些环境中出现这种情况。 在这种情况下,代码应该假定import jodie是指名为jodie的第三方或顶级包,而不是本地的jodie.py。

2.4 异常

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

2.4.1 定义

异常是跳出代码块正常控制流的一种方法以处理错误或其它异常情况。

2.4.2 优点

操作代码的正常控制流不因错误处理代码而变得杂乱无章。 它还允许控制流在发生特定情况时跳过多个帧,例如一步从 N 个嵌套的函数返回,而不必带上所有错误码。

2.4.3 缺点

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

2.4.4 结论

异常必须遵循某些条件:

2.5 全局变量

避免全局变量。

2.5.1 定义

在模块级别或作为类属性声明的变量。

2.5.2 优点

偶尔有用。

2.5.3 缺点

可能会在导入期间更改模块行为,因为对全局变量的赋值在模块首次导入时完成。

2.5.4 结论

避免全局变量。

尽管在技术上是变量,但它们允许并鼓励使用模块级常量。 例如:MAX_HOLY_HANDGRENADE_COUNT = 3 必须使用带有下划线的所有大写字母命名常量。 请参阅下面的命名

如果需要,应在模块级别声明全局变量,并通过加上_前缀在变成模块内部变量。 外部访问必须通过公开的模块级函数完成。 请参阅下面的命名

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

嵌套局部函数或类在用于屏蔽局部变量时是可以的。 内部类可以使用。

2.6.1 定义

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

2.6.2 优点

允许定义仅在非常有限的作用域内使用的实用程序类和函数。 非常ADT-y。 通常用于实现装饰器。

2.6.3 缺点

嵌套或局部类的实例不能pickle。 嵌套函数和类不能直接测试。 嵌套可能使外部的函数更长且可读性降低。

2.6.4 结论

它们可以使用,但有一些注意事项。 除非是屏蔽局部值,请避免嵌套函数或类。 不要只是为了向模块的用户隐藏它而嵌套一个函数。 相反,在模块级别用 _ 前缀名称,以便测试仍可以访问它。

2.7 解析和生成器表达式

可以用于简单的情形。

2.7.1 定义

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

2.7.2 优点

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

2.7.3 缺点

复杂的解析或生成器表达可能很难阅读。

2.7.4 结论

可以用于简单的情形。 每个部分的全部内容必须位于一行中:mapping 表达式、for子句、filter 表达式。 不允许使用多个for子句或 filter 表达式。 当事情变得更加复杂时,请使用循环。

正确
  result = [mapping_expr for value in iterable if filter_expr]

  result = [{'key': value} for value in iterable
            if a_long_filter_expression(value)]

  result = [complicated_transform(x)
            for x in iterable if predicate(x)]

  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}

  squares_generator = (x**2 for x in range(10))

  unique_names = {user.name for user in users if user is not None}

  eat(jelly_bean for jelly_bean in jelly_beans
      if jelly_bean.color == 'black')
错误
  result = [complicated_transform(
                x, some_argument=x+1)
            for x in iterable if predicate(x)]

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

不能通过读取方法名称来判断对象的类型(例如 has_key() 表示是一个字典)。 这也是一个优势。

2.8.4 结论

对于支持它们的类型(如列表、字典和文件),请使用默认迭代器和运算符。 内置类型也定义了迭代器方法。 优先使用这些方法而不是返回列表的方法,这种情况下你在对容器进行迭代时不应对其进行改变。 除非必要,切勿使用特定于 Python 2 的迭代方法,如dict.iter*()

正确  for key in adict: ...
      if key not in adict: ...
      if obj in alist: ...
      for line in afile: ...
      for k, v in adict.items(): ...
      for k, v in six.iteritems(adict): ...
错误   for key in adict.keys(): ...
      if not adict.has_key(key): ...
      for line in afile.readlines(): ...
      for k, v in dict.iteritems(): ...

2.9 生成器

根据需要使用生成器。

2.9 定义

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

2.9.2 优点

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

2.9.3 缺点

没有。

2.9.4 结论

可以使用。 在生成器函数的文档字符串中使用"Yields:"而不是"Returns:"。

2.10 Lambda 函数

适合单行代码。

2.10.1 定义

Lambda 用一个表达式定义匿名函数,而不是语句。 它们通常用于定义高阶函数(如map()filter())的回调或运算符。

2.10.2 优点

方便。

2.10.3 缺点

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

2.10.4 结论

可以把它们用于单行代码。 如果 lambda 函数内的代码长于 60-80 个字符,则最好将其定义为常规的嵌套函数

对于乘法等常见操作,请使用operator中的函数,而不是 lambda 函数。 例如,相对于lambda x, y: x * y,优选operator.mul

2.11 条件表达式

可以用于简单的情形。

2.11.1 定义

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

2.11.2 优点

比 if 语句更短、更方便。

2.11.3 缺点

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

2.11.4 结论

可以用于简单的情形。 每个部分必须适合一行:true 表达式、if 表达式、else 表达式。 当事情变得更复杂时,使用完整的 if 语句。

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

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

2.12.2 优点

通常你会遇到一个使用大量默认值的函数,但在极少数情况下,你希望覆盖默认值。 默认参数值提供了一种简单的方法来执行此操作,而无需为罕见的异常定义大量函数。 由于 Python 不支持重载的方法/函数,因此默认参数是"伪造"重载行为的简便方法。

2.12.3 缺点

默认参数在模块加载时计算一次。 如果参数是可变对象(如列表或字典),则这可能会导致问题。 如果函数修改这个对象(例如通过将一个元素追加到列表中),则将修改默认值。

2.12.4 结论

可以使用,但请遵循以下注意事项:

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

正确 def foo(a, b=None):
         if b is None:
             b = []
正确 def foo(a, b: Optional[Sequence] = None):
         if b is None:
             b = []
正确 def foo(a, b: Sequence = ()):  # Empty tuple OK since tuples are immutable
         ...
错误  def foo(a, b=[]):
         ...
错误  def foo(a, b=time.time()):  # The time the module was loaded???
         ...
错误  def foo(a, b=FLAGS.my_thing):  # sys.argv has not yet been parsed...
         ...
错误  def foo(a, b: Mapping = {}):  # Could still get passed to unchecked code
         ...

2.13 Properties

对于通常会使用简单、轻量级方法访问和设置的数据,可以用 properties 来访问和设置它们。

2.13.1 定义

一种封装方法调用的方式,用于当计算比较轻量时以标准属性访问的方式获取和设置属性。

2.13.2 优点

通过消除对简单属性访问的显式 get 和 set 方法调用,提高了可读性。 允许延迟计算。 被认为是维护类接口的 Pythonic 方法。 在性能方面,当直接访问变量是合理的时候,它允许属性绕过需要的琐碎访问方法。 它还允许在将来添加访问方法,而不会破坏这个接口。

2.13.3 缺点

Python 2 中必须从 object 继承。 可以隐藏副作用,就像运算符过载一样。 子类可能会令人困惑。

2.13.4 结论

在新代码中,对于通常会使用简单、轻量级方法访问和设置的数据,可以用 properties 来访问和设置它们。 应使用@property装饰器创建 properties。

如果 property 本身未重写,则 properties 的继承可能不明显。 因此,必须确保间接调用访问方法,以确保在子类中重写的方法被 property 调用(使用模板方法 DP)。

正确 import math

     class Square(object):
         """A square with two properties: a writable area and a read-only perimeter.

         To use:
         >>> sq = Square(3)
         >>> sq.area
         9
         >>> sq.perimeter
         12
         >>> sq.area = 16
         >>> sq.side
         4
         >>> sq.perimeter
         16
         """

         def __init__(self, side):
             self.side = side

         @property
         def area(self):
             """Area of the square."""
             return self._get_area()

         @area.setter
         def area(self, area):
             return self._set_area(area)

         def _get_area(self):
             """Indirect accessor to calculate the 'area' property."""
             return self.side ** 2

         def _set_area(self, area):
             """Indirect setter to set the 'area' property."""
             self.side = math.sqrt(area)

         @property
         def perimeter(self):
             return self.side * 4

2.14 True/False 计算

尽可能使用“隐式”的 False。

2.14.1 定义

Python 在布尔上下文中将某些值计算为False 快速的"经验法则"是所有"空"值都被视为 False,因此 0, None, [], {}, '' 在布尔上下文中都计算为 False。

2.14.2 优点

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

2.14.3 缺点

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

2.14.4 结论

如果可能,请使用“隐式”的 False,例如 if foo: 而不是 if foo != []: 不过,你应牢记以下几点注意事项:

2.15 已弃用的语言功能

尽可能使用字符串方法而不是string模块。 使用函数调用语法而不是apply 使用列表解析和for循环,而不是filtermap,虽然函数参数将会是一个内联 lambda。 使用for循环,而不用reduce

2.15.1 定义

当前版本的 Python 提供人们通常认为更可取的结构。

2.15.2 结论

我们不使用不支持这些功能的任何 Python 版本,因此没有理由不使用新风格。

正确 words = foo.split(':')

     [x[1] for x in my_list if x[2] == 5]

     map(math.sqrt, data)    # Ok. No inlined lambda expression.

     fn(*args, **kwargs)
No:  words = string.split(foo, ':')

     map(lambda x: x[1], filter(lambda x: x[2] == 5, my_list))

     apply(fn, args, kwargs)

2.16 词法作用域

可以使用。

2.16.1 定义

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

此功能的使用示例是:

def get_adder(summand1):
    """Returns a function that adds numbers to a given number."""
    def adder(summand2):
        return summand1 + summand2

    return adder

2.16.2 优点

通常会导致更清晰、更优雅的代码。 特别让经验丰富的 Lisp 和 Scheme(以及 Haskell 和 ML 等)的程序员感到欣慰。

2.16.3 缺点

可能导致令人困惑的错误。 例如,下面的示例基于PEP-0227

i = 4
def foo(x):
    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 函数和方法装饰器

如果有明显的优势,请恰当地使用装饰器。 避免@staticmethod,限制使用@classmethod

2.17.1 定义

函数和方法的装饰器(即@符号)。 一个常见的装饰器是@property,用于将普通方法转换为动态计算的属性。 但是,装饰器语法也允许用户定义装饰器。 具体来说,对于某个函数my_decorator,如下:

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

等效于:

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

2.17.2 优点

优雅地指定方法上的某种变换;这种变换可以消除一些重复的代码、强制执行不变式等。

2.17.3 缺点

装饰器可以对函数的参数或返回值执行任意操作,从而导致令人惊讶的隐式行为。 此外,装饰器在导入时执行。 装饰器代码中的错误几乎不可能恢复。

2.17.4 结论

如果有明显的优势,请恰当地使用装饰器。 装饰器应遵循与函数相同的导入和命名准则。 装饰器 pydoc 应明确说明该函数是装饰器。 编写装饰器的单元测试。

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

装饰器是"顶级代码"的特例 — 更多讨论请参阅main

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

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

2.18 Threading

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

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

使用 Queue 模块的Queue数据类型作为在线程之间通信数据的首选方式。 否则,请使用 threading 模块及其锁原语。 了解条件变量的正确使用,以便可以使用threading.Condition,而不是使用较低级别的锁。

2.19 威力过大的特性

避免使用这些特性。

2.19.1 定义

Python 是一种非常灵活的语言,它为你提供许多奇特的特性,如自定义元类、访问字节码、动态编译、动态继承、对象重定义父类、导入截获、反射(例如getattr()的一些用法)、系统内部的修改等。

2.19.2 优点

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

2.19.3 缺点

在并非绝对必要时使用这些“炫酷”的特性非常诱人。 很难阅读、理解和调试使用不常见特性的代码。 起初(对原始作者来说)似乎不是那样,但是当重新回顾代码时,它往往比更长但简单的代码更难。

2.19.4 决定

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

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

2.20 现代 Python:Python 3 和从__future__导入

Python 3 来了! 虽然不是每个项目都准备好使用它,但所有代码都应编写为与 3 兼容(如果可能,应在 Python 3 下进行测试)。

2.20.1 定义

Python 3 是 Python 语言中的一个显著变化。 虽然现有代码的编写通常考虑 2.7,但需要执行一些简单的操作,以使代码更明确地了解其意图,从而更好地准备在 Python 3 下使用而无需修改。

2.20.2 优点

在项目的所有依赖项准备就绪后,使用 Python 3 编写的代码更加明确,更易于在 Python 3 下运行。

2.20.3 缺点

有些人觉得这些额外的样板代码很丑。 将导入添加到实际上不需要导入添加的功能的模块并不常见。

2.20.4 结论

从__future__导入

鼓励使用from __future__ import语句。 所有新代码都应包含以下内容,并且现有代码应尽可能更新为与之兼容:

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

如果你还不熟悉这些,请在此处阅读每个内容:绝对导入新的/除法行为print函数

请不要省略或删除这些导入,即使它们当前未在模块中使用,除非代码仅为 Python 3 编写。 最好始终在所有文件中导入 future,以便在以后有人开始使用此类功能时不会忘记。

还有其它from __future__导入语句中。 尽可能使用它们。 我们的建议中不包括unicode_literals,因为它在 Python 2.7 中的很多地方引入了隐含的默认 codec 转换结果,因此这不是一个明显优胜的地方。 大多数代码最好根据需要显式使用b''u''表示字节和 unicode 字符串字面量。

six、future 和 past 库

当项目需要同时积极支持 Python 2 和 3 下的使用时,请使用你认为合适的sixfuturepast库。 它们的存在是为了让你的代码更简洁、更容易。

2.21 类型注解代码

你可以根据PEP-484对 Python 3 代码进行带类型提示的注解,并在构建时使用pytype等类型检查工具对代码进行类型检查。

类型注解可以位于源文件或stub pyi 文件 只要有可能,注解应位于源文件中。 将 pyi 文件用于第三方或扩展模块。

2.21.1 定义

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

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

你还可以使用特殊注释声明变量的类型:

a = SomeFunc()  # type: SomeType

2.21.2 优点

类型注解可提高代码的可读性和可维护性。 类型检查器将许多运行时错误在构建时提前发现,并降低使用威力过大的特性能力。

2.21.3 缺点

你必须使类型声明保持最新。 你可能会看到类型错误是你认为有效的代码。 使用类型检查器可能会降低您使用电源功能的能力。

2.21.4 结论

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

3 Python 风格规范

3.1 分号

不要用分号终止行,也不要使用分号将两个语句放在同一行上。

3.2 行的长度

一行最大长度为80个字符

80 个字符限制的明确例外:

with语句除非需要三个或更多上下文管理器,不要使用反斜杠续行。

使用 Python 圆括号、方括号和花括号内部的隐式行连接 如有必要,可以在表达式周围添加一对额外的括号。

正确 foo_bar(self, width, height, color='black', design=None, x='foo',
             emphasis=None, highlight=0)

     if (width == 0 and height == 0 and
         color == 'red' and emphasis == 'strong'):

当一个文本字符串超过一行时,请使用括号进行隐式行联接。

x = ('This will build a very long long '
     'long long long long long long string')

在注释中,如有必要将长 URL 放在单独的一行上。

Yes:  # See details at
      # http://www.example.com/us/developer/documentation/api/content/v2.0/csv_file_name_extension_full_specification.html
No:  # See details at
     # http://www.example.com/us/developer/documentation/api/content/\
     # v2.0/csv_file_name_extension_full_specification.html

在定义表达式跨越三行或多行的with语句时,允许使用反斜杠续行。 对于两行表达式,请使用嵌套with语句:

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)
No:  with VeryLongFirstExpressionFunction() as spam, \
          VeryLongSecondExpressionFunction() as beans:
       PlaceOrder(eggs, beans, spam, beans)
Yes:  with very_long_first_expression_function() as spam:
          with very_long_second_expression_function() as beans:
              place_order(beans, spam)

记下上述续行示例中的元素的缩进;有关说明,请参阅缩进部分。

在所有其它情况下,如果行超过 80 个字符,并且yapf自动格式化不能帮助使行低于限制,则允许行超过此最大值。

3.3 括号

括号的使用应宁缺毋滥。

在元组周围使用括号是可以的,虽然不是必须的。 在返回语句或条件语句中不要使用它们,除非使用括号表示隐式续行或表示一个元组。

正确 if foo:
         bar()
     while x:
         x = bar()
     if x and y:
         bar()
     if not x:
         bar()
     # For a 1 item tuple the ()s are more visually obvious than the comma.
     onesie = (foo,)
     return foo
     return spam, beans
     return (spam, beans)
     for (x, y) in dict.items(): ...
错误  if (x):
         bar()
     if not(x):
         bar()
     return (foo)

3.4 缩进

4 个空格 缩进代码块。

切勿使用 Tab 或混合 Tab 和空格。 在隐含线延续的情况下,您应该垂直对齐包装的元素,如行长度部分中的示例所示;或使用 4 个空格的悬挂缩进,在这种情况下,在第一行的打开括号或括号后不应有任何内容。

正确   # Aligned with opening delimiter
       foo = long_function_name(var_one, var_two,
                                var_three, var_four)
       meal = (spam,
               beans)

       # Aligned with opening delimiter in a dictionary
       foo = {
           long_dictionary_key: value1 +
                                value2,
           ...
       }

       # 4-space hanging indent; nothing on first line
       foo = long_function_name(
           var_one, var_two, var_three,
           var_four)
       meal = (
           spam,
           beans)

       # 4-space hanging indent in a dictionary
       foo = {
           long_dictionary_key:
               long_dictionary_value,
           ...
       }
错误    # Stuff on first line forbidden
       foo = long_function_name(var_one, var_two,
           var_three, var_four)
       meal = (spam,
           beans)

       # 2-space hanging indent forbidden
       foo = long_function_name(
         var_one, var_two, var_three,
         var_four)

       # No hanging indent in a dictionary
       foo = {
           long_dictionary_key:
           long_dictionary_value,
           ...
       }

3.4.1 元素序列最后的逗号?

仅当容器结束符号])}与最后一个元素不是出现在相同的行上时,才建议在元素序列的最后加上逗号。 最后一个逗号的存在也用作 Python 代码自动格式化器YAPF的提示,当最后一个元素后面的,存在时,它指示自动化格式器自动将每个元素放在一行。

Yes:   golomb3 = [0, 1, 3]
Yes:   golomb4 = [
           0,
           1,
           4,
           6,
       ]
No:    golomb4 = [
           0,
           1,
           4,
           6
       ]

3.5 空白行

顶级定义之间有两个空白行,它们可能是函数定义或类定义。 方法定义之间以及class行和第一个方法之间有一个空白行。 def行之后没有空白行。 在函数或方法中判断适当时,请使用单个空白行。

3.6 空格

遵循标准排版规则来使用标点符号前后的空格。

圆括号、方括号和花括号的内侧没有空格。

Yes: spam(ham[1], {eggs: 2}, [])
No:  spam( ham[ 1 ], { eggs: 2 }, [ ] )

逗号、分号或冒号之前没有空格。 在逗号、分号或冒号之后使用空格,但行尾除外。

Yes: if x == 4:
         print(x, y)
     x, y = y, x
No:  if x == 4 :
         print(x , y)
     x , y = y , x

作为一个参数列表、索引或切片开始的左圆括号/左方括号的前面没有空格。

Yes: spam(1)
No:  spam (1)
Yes: dict['key'] = list[index]
No:  dict ['key'] = list [index]

无尾随空格。

二元运算符的两侧各有一个空格,如赋值(=)、比较(==, <, >, !=, <>, <=, >=, in, not in, is, is not) 和布尔运算(and, or, not)。 算术运算符的两侧空格你自己好好判断(+, -, *, /, //, %, **, @)。

Yes: x == 1
No:  x<1

在传递关键字参数或定义默认参数值时,切勿在=周围使用空格,但有一个例外:当存在类型注解时,对默认参数值一定要=两侧使用空格。

正确 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)
错误  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)

不要使用空格在相邻的行上垂直对齐标记,因为它成为维护负担(适用于:, #, =等):

Yes:
  foo = 1000  # comment
  long_name = 2  # comment that should not be aligned

  dictionary = {
      'foo': 1,
      'long_name': 2,
  }
No:
  foo       = 1000  # comment
  long_name = 2     # comment that should not be aligned

  dictionary = {
      'foo'      : 1,
      'long_name': 2,
  }

3.7 Shebang 行

大多数.py文件不需要以一个 #! 行开始。 使用#!/usr/bin/python启动程序的主文件,每个PEP-394具有可选的个位数23后缀。

内核使用此行查找 Python 解释器,但在导入模块时被 Python 忽略。 仅在直接执行的文件上需要这样做。

3.8 注释和文档字符串

请务必对模块、函数、方法的文档字符串和内联注释使用正确的风格。

3.8.1 文档字符串

Python 使用文档字符串 来文档化代码。 文档字符串是一个字符串,它是包、模块、类或函数中的第一个语句。 这些字符串可以通过对象的__doc__成员自动提取,并由pydoc使用。 (在你的模块上试着运行一下pydoc,看看它是什么样子。) 始终对文档字符串使用三个双引号"""格式(根据PEP 257)。 文档字符串应组织为摘要行(一根物理行),由句点、问号或感叹号结束,后跟空白行,后跟文档字符串的其余部分,从第一行的第一个引号相同的光标位置开始。 下面还有更多文档字符串的格式设置指南。

3.8.2 模块

每个文件都应包含许可证样板。 为项目使用的许可证选择适当的样板(例如,Apache 2.0、BSD、LGPL、GPL)

文件应以描述模块的内容和用法的文档字符串开头。

"""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
contain a brief description of exported classes and functions and/or usage
examples.

  Typical usage example:

  foo = ClassFoo()
  bar = foo.FunctionBar()
"""

3.8.3 函数和方法

在本节中,"函数"是指方法、函数或生成器。

函数必须具有文档字符串,除非它满足以下所有条件:

文档字符串应提供足够的信息使得无需阅读函数的代码就可以编写对函数的调用。 文档字符串应具有描述性风格("""Fetches rows from a Bigtable.""")而不是命令式("""Fetch rows from a Bigtable."""),但@property数据描述符除外,该描述符应使用与属性相同的风格 文档字符串应描述函数的调用语法及其语义,而不是其实现。 对于难懂的代码,代码旁的注释比使用文档字符串更合适。

重写基类方法的方法可能具有一个简单的文档字符串,将读者引到其重写的方法的文档字符串,例如"""See base class.""" 其理由是,在很多地方不需要重复基本方法的文档字符串中已经存在的文档。 但是,如果重写的方法的行为与被重写的方法有很大不同,或者需要提供详细信息(例如,记录其它副作用),则重写的方法上至少需要具有这些差异的文档字符串。

关于函数的几个方面应该在特定的小节中描述记录,如下所示。 每个部分以标题行开头,以冒号结尾。 标题以外的所有部分都应保留两个或四个空格的缩进(在同一文件中保持一致)。 如果函数的名称和签名信息量足够丰富,以至于可以使用单行文档字符串恰当地描述,则可以省略这些部分。

Args:
按名称列出每个参数。 描述应与名称相符,并用冒号和空格分隔。 如果描述太长,无法容纳在一个 80 个字符的行上,请使用 2 或 4 个空格的缩进(与文件的其余部分一致)。

如果代码不包含相应的类型注解,则说明应包括所要求的类型。 如果函数接受*foo(可变长度参数列表)和/或**bar(任意关键字参数),则应将它们列为*foo**bar

Returns: (or Yields: for generators)
描述返回值的类型和语义。 如果函数仅返回 None,则此部分不是必需的。 如果文档字符串以 Returns 或 Yields 开头(例如"""Returns row from Bigtable as a tuple of strings.""")并且这个开头的语句足以描述返回值,则也可以省略它。
Raises:
列出与这个接口相关的所有异常。 不应记录违背文档字符串中指定的 API 时所引发的异常(因为应在 API 里处理违背 API 的行为)。
def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):
    """Fetches rows from a Bigtable.

    Retrieves rows pertaining to the given keys from the Table instance
    represented by big_table.  Silly things may happen if
    other_silly_variable is not None.

    Args:
        big_table: An open Bigtable Table instance.
        keys: A sequence of strings representing the key of each table row
            to fetch.
        other_silly_variable: Another optional variable, that has a much
            longer name than the other args, and which does nothing.

    Returns:
        A dict mapping keys to the corresponding table row data
        fetched. Each row is represented as a tuple of strings. For
        example:

        {'Serak': ('Rigel VII', 'Preparer'),
         'Zim': ('Irk', 'Invader'),
         'Lrrr': ('Omicron Persei 8', 'Emperor')}

        If a key from the keys argument is missing from the dictionary,
        then that row was not found in the table.

    Raises:
        IOError: An error occurred accessing the bigtable.Table object.
    """

3.8.4 类

类应在描述类的类定义下方具有文档字符串。 如果你的类具有公开属性,则应在Attributes部分中进行记录,并采用与函数的Args部分相同的格式。

class SampleClass(object):
    """Summary of class here.

    Longer class information....
    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=False):
        """Inits SampleClass with blah."""
        self.likes_spam = likes_spam
        self.eggs = 0

    def public_method(self):
        """Performs operation blah."""

3.8.5 注释块和内联注释

最后需要添加注释的位置是代码的难懂部分。 如果你要在下一次代码评审时解释它,你应该现在就添加注释。 复杂的操作在开始之前加几行注释。 非显而易见的操作在行尾加上注释。

# 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 and then do binary search to
# get the exact number.

if i & (i-1) == 0:  # True if i is 0 or a power of 2.

为了提高可读性,这些注释应从注释字符#的代码开始至少 2 个空格,然后至少一个空格位于注释本身的文本之前。

另一方面,切勿描述代码。 假设阅读代码的人比你更了解 Python(虽然不是你正在做什么)。

# BAD COMMENT: Now go through the b array and make sure whenever i occurs
# the next element is i+1

3.8.6 标点、拼写和语法

注意标点符号、拼写和语法;阅读写得好的评论比写得不好的评论容易。

注释应与叙述文本一样可读,具有适当的大写和标点符号。 在许多情况下,完整的句子比句子片段更具可读性。 较短的注释(如代码行末尾的注释)有时可能不太正式,但你应该保持一致的风格。

尽管代码审阅者指出在应该使用分号时使用逗号可能令人沮丧,但源代码保持高度的清晰度和可读性非常重要。 正确的标点符号、拼写和语法有助于实现这一目标。

3.9 类

如果类从没有其他基类继承,则显式从object继承。 这也适用于嵌套类。

正确 class SampleClass(object):
         pass


     class OuterClass(object):

         class InnerClass(object):
             pass


     class ChildClass(ParentClass):
         """Explicitly inherits from another class already."""

No: class SampleClass:
        pass


    class OuterClass:

        class InnerClass:
            pass

需要从object继承以使属性在 Python 2 中正常工作,并可以保护你的代码免受与 Python 3 的潜在不兼容。 它还定义了实现对象默认语义的特殊方法,包括__new__, __init__, __delattr__, __getattribute__, __setattr__, __hash__, __repr____str__

3.10 字符串

使用format方法或%运算符对字符串进行格式化,即使参数都是字符串。 不过,最好用你的判断力来决定用+%(或format)。

正确 x = a + b
     x = '%s, %s!' % (imperative, expletive)
     x = '{}, {}'.format(first, second)
     x = 'name: %s; score: %d' % (name, n)
     x = 'name: {}; score: {}'.format(name, n)
     x = f'name: {name}; score: {n}'  # Python 3.6+
No: x = '%s%s' % (a, b)  # use + in this case
    x = '{}{}'.format(a, b)  # use + in this case
    x = first + ', ' + second
    x = 'name: ' + name + '; score: ' + str(n)

避免使用++=运算符在循环中累积字符串。 由于字符串是不可变的,这会产生不必要的临时对象,并产生二次方而不是线性的运行时间。 将每个子字符串添加到一个列表中,并在循环终止后''.join列表(或将每个子字符串写入io.BytesIO缓冲区)。

Yes: items = ['<table>']
     for last_name, first_name in employee_list:
         items.append('<tr><td>%s, %s</td></tr>' % (last_name, first_name))
     items.append('</table>')
     employee_table = ''.join(items)
No: employee_table = '<table>'
    for last_name, first_name in employee_list:
        employee_table += '<tr><td>%s, %s</td></tr>' % (last_name, first_name)
    employee_table += '</table>'

与文件中的字符串引号字符选择保持一致。 选择'"并沿用下去。 可以在字符串上使用另一个引号字符来避免需要在字符串中\\转义。

Yes:
  Python('Why are you hiding your eyes?')
  Gollum("I'm scared of lint errors.")
  Narrator('"Good!" thought a happy Python reviewer.')
No:
  Python("Why are you hiding your eyes?")
  Gollum('The lint. It burns. It burns us.')
  Gollum("Always the great lint. Watching. Watching.")

"""表示多行字符串,而不是''' 项目可以选择对所有非文档字符串多行字符串使用''',当且仅当它们也对常规字符串使用' 无论如何,文档字符串都必须使用"""

多行字符串不会随程序其余部分的缩进而流动。 如果你需要避免在字符串中嵌入多余的空格,请使用串联的单行字符串或带textwrap.dedent()的多行字符串删除每行的初始空格:

  错误
  long_string = """This is pretty ugly.
Don't do this.
"""
  Yes:
  long_string = """This is fine if your use case can accept
      extraneous leading spaces."""
  Yes:
  long_string = ("And this is fine if you can not accept\n" +
                 "extraneous leading spaces.")
  Yes:
  long_string = ("And this too is fine if you can not accept\n"
                 "extraneous leading spaces.")
  Yes:
  import textwrap

  long_string = textwrap.dedent("""\
      This is also fine, because textwrap.dedent()
      will collapse common leading spaces in each line.""")

3.11 文件和套接字

使用文件与套接字完成时,显式关闭它们。

让文件、套接字或其它类似文件对象不必要地开着有很多弊端:

此外,虽然文件和套接字在文件对象被析构时会自动关闭,但将文件对象的生存期与文件的状态绑定的做法很差:

管理文件的首选方法是使用“with”语句

with open("hello.txt") as hello_file:
    for line in hello_file:
        print(line)

对于不支持"with"语句的类文件对象,请使用contextlib.closing()

import contextlib

with contextlib.closing(urllib.urlopen("http://www.python.org/")) as front_page:
    for line in front_page:
        print(line)

3.12 TODO 评论

TODO注释用于临时代码、短期解决方案或足够好但并非完美的代码。

TODO注释以所有字母大写的字符串TODO开头,接着是用括号括起来的姓名、电子邮件地址、其它个人标识符、或者与问题最相关的上下文。 之后是说明要做什么。

其目的是具有一致的TODO格式,可以搜索以了解如何获取更多详细信息。 TODO不是相关人员承诺将解决这个问题。 因此,当你创建一个TODO时,几乎总是写你自己的名字。

# TODO(kl@gmail.com): Use a "*" here for string repetition.
# TODO(Zeke) Change this to use relations.

如果你的TODO的格式为"将来执行某些操作",请确保包含非常具体的日期("2009 年 11 月修复")或非常具体的事件("当所有客户端都可以处理 XML 响应时删除此代码")。

3.13 导入格式

每个导入应位于单独的行上。

例如:

Yes: import os
     import sys
No:  import os, sys

导入始终放在文件的顶部,在任何模块注释和文档字符串之后,以及模块全局变量和常量之前。 导入的分组应从最通用到最不通用:

  1. Python future 导入语句。 例如:

    from __future__ import absolute_import
    from __future__ import division
    from __future__ import print_function
    

    有关这些的详细信息,请参阅上面

  2. Python 标准库导入。 例如:

    import sys
    
  3. 第三方模块或包导入。 例如:

    import tensorflow as tf
    
  4. 代码存储库子包导入。 例如:

    from otherproject.ai import mind
    
  5. 废弃: 与特定应用程序相关导入,与此文件属于相同顶级子包。 例如:

    from myproject.backend.hgwells import time_machine
    

    你可能会发现较旧的 Google Python 风格代码执行此操作,但不再需要它。 我们鼓励新代码不要为此烦恼。 只需将特定于应用程序的子包导入与其它子包导入相同对待。

在每个分组中,导入应按字典顺序排序,而忽略大小写,根据每个模块的完整包路径。 代码可以选择在导入部分之间放置一个空行。

import collections
import queue
import sys

from absl import app
from absl import flags
import bs4
import cryptography
import tensorflow as tf

from book.genres import scifi
from myproject.backend.hgwells import time_machine
from myproject.backend.state_machine import main_loop
from otherproject.ai import body
from otherproject.ai import mind
from otherproject.ai import soul

# Older style code may have these imports down here instead:
#from myproject.backend.hgwells import time_machine
#from myproject.backend.state_machine import main_loop

3.14 语句

通常每行只有一个语句。

但是,仅当整个语句适合一行时,你才能将真值测试放在同一行上。 特别是,永远不要对try/except这样做,因为tryexcept不能放在同一行,而且只在没有else的时候,才能这样使用if

Yes:

  if foo: bar(foo)
No:

  if foo: bar(foo)
  else:   baz(foo)

  try:               bar(foo)
  except ValueError: baz(foo)

  try:
      bar(foo)
  except ValueError: baz(foo)

3.15 访问函数

如果访问函数很短小,则应使用公开的变量而不是访问函数来避免 Python 中函数调用的额外成本。 添加更多功能后,可以使用property来保持语法一致。

另一方面,如果访问更为复杂或者访问变量的成本很大,则应使用函数调用如get_foo()set_foo()(遵循命名准则)。 如果过去的行为允许通过 property 进行访问,则不要将新的访问函数绑定到该属性。 任何仍在尝试通过旧方法访问变量的代码都应明显中断,以便它们了解复杂性的变化。

3.16 命名

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.

函数名称、变量名称和文件名应具有描述性;避免缩写。 特别是,不要使用项目以外的读者模棱两可或不熟悉的缩写,也不要通过删除单词中的字母来缩写。

始终使用.py文件名扩展名。 切勿使用破折号。

3.16.1 要避免的名称

3.16.2 命名约定

3.16.3 文件命名

Python 文件名必须具有.py扩展名,并且不能包含破折号(-)。 这允许它们被导入和单元测试。 如果你希望不带扩展名即可访问可执行文件,请使用符号链接或包含exec "$0.py" "$@"的简单 bash 封装。

3.16.4 从 Guido 的建议中得出的准则

类型 公开 内部
lower_with_under
模块 lower_with_under _lower_with_under
CapWords _CapWords
异常 CapWords
函数 lower_with_under() _lower_with_under()
全局/类常量 CAPS_WITH_UNDER _CAPS_WITH_UNDER
全局/类变量 lower_with_under _lower_with_under
实例变量 lower_with_under _lower_with_under (protected)
方法名称 lower_with_under() _lower_with_under() (protected)
函数/方法参数 lower_with_under
局部变量 lower_with_under

尽管 Python 支持通过使用前导双下划线__(又名 "dunder")前缀,但不鼓励这样做。 更鼓励使用单个下划线。 它们更易于键入、阅读和从小型单元测试访问。 Lint 警告处理对受保护成员的不可理访问。

3.17 Main

即使是打算用作可执行文件的文件也应该是可导入的,单纯的导入也不应具有执行程序主要功能的副作用。 主要功能应位于main()函数中。

在 Python 中,pydoc和单元测试要求模块是可导入的。 在执行主程序之前,代码应始终检查if __name__ == '__main__'以便在导入模块时不执行主程序。

def main():
    ...

if __name__ == '__main__':
    main()

导入模块时,将执行顶层的所有代码。 请注意,不要调用函数、创建对象或执行文件被pydoc调用时不应执行的其它操作。

3.18 函数长度

首选小型且重点突出的函数。

我们认识到,长函数有时是合适的,因此对函数长度没有硬性限制。 如果函数超过大约 40 行,则考虑是否可以在不损害程序结构的情况下将其分解。

即使你的长函数现在工作正常,在几个月内修改它的人可能会添加新的行为。 这可能会导致难以找到的 Bug。 保持函数简短和简单,使其他人更容易阅读和修改你的代码。

使用某些代码时,可能会发现长而复杂的函数。 不要被修改现有代码吓倒:如果使用这样的函数证明很困难,你发现错误很难调试,或者你希望在几个不同的上下文中使用它的一部分,请考虑将函数分解为更小和更多可管理件。

3.19 类型注解

3.19.1 一般规则

3.19.2 断行

尝试遵循现有的缩进规则。

有注解之后,许多函数签名将变成"每行一个参数"。

def my_method(self,
              first_var: int,
              second_var: Foo,
              third_var: Optional[Bar]) -> int:
  ...

总是倾向在变量之间而不是在变量名称和类型注释之间断行。 但是,如果一切都在同一行上,那就去吧。

def my_method(self, first_var: int) -> int:
  ...

如果函数名称、最后一个参数和返回类型的组合太长,则在新行中缩进 4。

def my_method(
    self, first_var: int) -> Tuple[MyLongType1, MyLongType1]:
  ...

当返回类型与最后一个参数不在同一行时,首选方法是在新行上将参数缩进4,并将右括号与 def 对齐。

正确
def my_method(
    self, other_arg: Optional[MyLongType]
) -> Dict[OtherLongType, MyLongType]:
  ...

pylint允许你将右括号移动到新行并与首行对齐,但读取性较差。

错误
def my_method(self,
              other_arg: Optional[MyLongType]
             ) -> Dict[OtherLongType, MyLongType]:
  ...

如上例所示,不要中断类型。 但是,有时它们太长,无法放在一行上(尝试使子类型保持不间断)。

def my_method(
    self,
    first_var: Tuple[List[MyLongType1],
                     List[MyLongType2]],
    second_var: List[Dict[
        MyLongType3, MyLongType4]]) -> None:
  ...

如果单个名称和类型太长,请考虑为该类型使用别名 最后一个手段是在冒号后中断并缩进 4 个空格。

正确
def my_function(
    long_variable_name:
        long_module_name.LongTypeName,
) -> None:
  ...
错误
def my_function(
    long_variable_name: long_module_name.
        LongTypeName,
) -> None:
  ...

3.19.3 远期声明

如果需要使用尚未定义的同一模块中的类名称(例如,如果需要类声明中的类,或者使用下面定义的类),请使用类名称的字符串。

class MyClass(object):

  def __init__(self,
               stack: List["MyClass"]) -> None:

3.19.4 默认值

根据PEP-008对同时具有类型注解和默认值的参数在=周围使用空格。

正确
def func(a: int = 0) -> int:
  ...
错误
def func(a:int=0) -> int:
  ...

3.19.5 NoneType

在 Python 类型系统中,NoneType是"第一等级"类型,为了方便输入NoneNoneType的别名。 如果参数可以是None,则必须声明它! 可以使用Union,但如果只有另一种类型,请使用Optional

使用显式Optional而不是隐式Optional 早期版本的 PEP 484 允许a: Text = None解释为a: Optional[Text] = None,但这不再是推荐的行为。

正确
def func(a: Optional[Text], b: Optional[Text] = None) -> Text:
  ...
def multiple_nullable_union(a: Union[None, Text, int]) -> Text
  ...
错误
def nullable_union(a: Union[None, Text]) -> Text:
  ...
def implicit_optional(a: Text = None) -> Text:
  ...

3.19.6 类型别名

你可以声明复杂类型的别名。 别名的名称应为 CapWorded。 如果仅在此模块中使用别名,则应为 _Private。

例如,如果模块的名称与类型的名称太长:

_ShortName = module_with_long_name.TypeWithLongName
ComplexMap = Mapping[Text, List[Tuple[int, int]]]

其他示例是复杂的嵌套类型和函数的多个返回变量(作为元组)。

3.19.7 忽略类型

您可以在带有特殊注释* 类型:忽略的行上禁用类型检查。

pytype对特定错误具有禁用选项(类似于lint):

# pytype: disable=attribute-error

3.19.8 类型变量

如果内部变量的类型很难或无法推断,则可以通过几种方式指定其类型。

类型注释:
使用 # type: 行尾的注释
a = SomeUndecoratedFunction()  # type: Foo
Annotated Assignments
使用冒号并在变量名称和值之间键入,就像函数参数一样。
a: Foo = SomeUndecoratedFunction()

3.19.9 元组与列表

与只能具有单一类型的列表不同,元组可以具有单一重复类型,也可以具有一定数量的具有不同类型的元素。 后者通常用作函数的返回类型。

a = [1, 2, 3]  # type: List[int]
b = (1, 2, 3)  # type: Tuple[int, ...]
c = (1, "2", 3.5)  # type: Tuple[int, Text, float]

3.19.10 TypeVars

Python 类型系统具有泛型 工厂函数TypeVar是使用它们的常用方法。

例子:

from typing import List, TypeVar
T = TypeVar("T")
...
def next(l: List[T]) -> T:
  return l.pop()

TypeVar 可以受到限制:

AddableType = TypeVar("AddableType", int, float, Text)
def add(a: AddableType, b: AddableType) -> AddableType:
  return a + b

typing模块中常见的预定义类型变量是AnyStr 将它用于多个注解,这些注解可以是bytesunicode,并且必须全部为同一类型。

from typing import AnyStr
def check_length(x: AnyStr) -> AnyStr:
  if len(x) <= 42:
    return x
  raise ValueError()

3.19.11 字符串类型

字符串注解的正确类型取决于代码用于的 Python 版本。

对于仅限 Python 3 的代码,最好使用str Text也是可以接受的。 在使用其中一种时保持一致。

对于 Python 2 兼容代码,请使用Text 在极少数情况下,str可能有意义;通常,当两个 Python 版本之间的返回类型不同时,有助于兼容性。 避免使用unicode:它不存在于 Python 3 中。

之所以存在这种差异,是因为str表示不同的东西,具体取决于 Python 版本。

错误
def py2_code(x: str) -> unicode:
  ...

对于处理二进制数据的代码,请使用bytes

def deals_with_binary_data(x: bytes) -> bytes:
  ...

对于处理文本数据的 Python 2 兼容代码(在 Python 2 中为strunicode,在 Python 3 中为str),请使用Text 对于仅处理文本数据的代码,首选str

from typing import Text
...
def py2_compatible(x: Text) -> Text:
  ...
def py3_only(x: str) -> str:
  ...

如果类型可以是字节或文本,请使用具有相应文本类型的Union。

from typing import Text, Union
...
def py2_compatible(x: Union[bytes, Text]) -> Union[bytes, Text]:
  ...
def py3_only(x: Union[bytes, str]) -> Union[bytes, str]:
  ...

如果函数的所有字符串类型始终相同,例如,如果返回类型与上述代码中的参数类型相同,请使用AnyStr

像这样编写它将简化将代码移植到 Python 3 的过程。

3.19.12 导入用于键入

对于来自typing模块的类,始终导入类本身。 允许你显式在一行上从typing模块导入多个特定的类。 例如:

from typing import Any, Dict, Optional

鉴于从typing导入的这种方式会将元素添加到局部名称空间,因此typing中的任何名称都应与关键字类似地对待,并且不要在你的 Python 代码中定义。 如果类型与模块中的现有名称之间存在冲突,请使用import x as y导入。

from typing import Any as AnyType

3.19.13 条件导入

仅在必须避免在运行时避免类型检查所需的额外导入的例外情况下使用条件导入。 不建议使用此模式;应首选替代项,例如重构代码以允许顶级导入。

仅类型注释所需的导入可以放置在 如果TYPE_CHECKING: 块。

3.19.14 循环依赖项

键入引起的循环依赖项是代码气味。 此类代码是重构的好候选代码。 尽管从技术上讲,可以保留循环依赖项,但生成系统不会允许您这样做,因为每个模块必须依赖于其他模块。

将创建循环依赖项导入的模块替换为Any。 使用有意义的名称设置别名,并使用此模块中的实类型名称(Any 是 Any 的任何属性)。 别名定义应与上次导入分开一行。

from typing import Any

some_mod = Any  # some_mod.py imports this module.
...

def my_method(self, var: some_mod.SomeType) -> None:
  ...

3.19.15 通用

进行分称时,最好为泛型类型指定类型参数;否则,泛型的参数将假定为Any。

def get_names(employee_ids: List[int]) -> Dict[int, Any]:
  ...
# These are both interpreted as get_names(employee_ids: List[Any]) -> Dict[Any, Any]
def get_names(employee_ids: list) -> Dict:
  ...

def get_names(employee_ids: List) -> Dict:
  ...

如果泛型的最佳类型参数为Any,请将其显式化,但请记住,在许多情况下TypeVar可能更合适:

def get_names(employee_ids: List[Any]) -> Dict[Any, Text]:
  """Returns a mapping from employee ID to employee name for given IDs."""
T = TypeVar('T')
def get_names(employee_ids: List[T]) -> Dict[T, Text]:
  """Returns a mapping from employee ID to employee name for given IDs."""

4 临别赠言

保持一致

如果要编辑代码,请花几分钟时间查看周围的代码并确定其风格。 如果它们在所有算术运算符两侧都使用空格,那么你也应该这样做。 如果它们的评论周围有小框的哈希标记,让你的评论周围也有小框的哈希标记。

制定风格指南的目的是要有一个通用的编码规范,以便人们可以专注于你在说什么而不是你是如何说的。 我们在这里呈现整体风格规则,让人们知道这些规范,但是当前风格也很重要。 如果添加到文件中的代码看起来与文件周围的现有代码大不相同,则读者在阅读时会偏离其节奏。 避免这种情况。