PEP8 规范

PEP 8

Posted by Bryan on January 6, 2019

基础

本篇文章大致是对PEP 8 的翻译,部分内容没有严格按照逐句的翻译方式。希望能写出更加Pythonic的代码

代码布局

缩进

每个缩进层次为4个空格

连续行包装的元素的两种方案:

  1. 隐式续行 (implicit line),垂直对齐与圆括号,方括号或花括号
  2. 悬挂缩进(hanging indent),首行没有参数,后续行应该多缩进一级以便和正常的缩进区分
# 隐式续行
foo = long_function_name(var_one, var_two,
                         var_three, var_four)
# 悬挂缩进,方法参数多缩进一级
def long_function_name(
        var_one, var_two, var_three,
        var_four):
    print(var_one)

Tab 或 空格

建议使用空格缩进

唯一的一种情况下建议使用Tab:之前的代码已经使用了Tab,为了维持一致性

单行最大长度

单行最大长度不超过79个字符

有更少的结构限制的连续大段文字,单行长度限制在72个字符

二元操作符之前或之后换行

两种方式都是被允许的,但是从可读性的角度,建议在二元操作符之前换行,代码如下所示:

income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)

空行

  • 顶层方法或类定义使用两个空行
  • 类中方法使用单个空行
  • 在方法内部,使用空行表示逻辑区域(尽量少)

源文件编码

Python源代码建议为UTF-8(在python 2也可以设置为ASCII)

Python 2中使用ASCII,在Python3中使用UTF-8编码,都不应该添加编码声明

导入

  • 导入应该每行一个,但是从单个模块中导入多个部分是可以写在一行上
# 多个模块的导入应该每行一个
import os
import sys
# 从单个模块导入多个部分可以写在一行
from subprocess import Popen, PIPE
  • 导入代码应该放在文件顶部,在模块注释和文档字符串(docstring)之后,在模块全局变量和常量之前

导入代码应该按照下面的顺序分组:

  1. 标准库导入
  2. 相关的第三方库导入
  3. 本地应用/库导入

在组导入之间使用空行隔开

  • 一般情况下,推荐使用绝对导入,除此以外,显式相对导入也可以接受,特别是在处理复杂的包结构,而绝对导入显得冗余的情况下。而隐式相对导入应该禁止使用
# 绝对导入,推荐
import mypkg.sibling
from mypkg.sibling import example
# 显式相对引用,可接受
from . import sibling
from .sibling import example

而隐式相对导入则是不指定相对的目录,直接使用import sibling 去引用同一个包下面的模块,这样方式是不推荐的。

  • 通配符导入应该避免,类似 from module import *

模块级双下划线命名

模块级别双下划线变量比如__all__, __author__ 应该别放在模块文档字符串之后,但是需要放在import语句之前。但是存在一个例外,from __future__ import 应该放在双下划线变量之前。代码如下所示:

# 文档字符串,放在最前面
"""This is the example module.

This module does stuff.
"""

# 特殊情况,__future__ 导入必须放在除了文档字符串之后的所有代码前面
from __future__ import barry_as_FLUFL

# 双下划线变量,必须放在导入代码之前
__all__ = ['a', 'b', 'c']
__version__ = '0.1'
__author__ = 'Cardinal Biggles'

# 正常导入代码
import os
import sys

字符串引号

在Python中,单引号字符串和双引号字符串时一样的。但是不推荐随意使用。使用其中一种,坚持使用。当字符串中包含单引号或双引号时,字符串引号使用另一种,从而避免使用反斜杠。

对于三引用的字符串,推荐使用双引号字符从而与文档字符串保持一致性

表达式和语句中的空格

令人讨厌的

在下面情况下应该避免额外的空格

  • 在方括号,圆括号,大括号之后
# 正确
spam(ham[1], {eggs: 2})

# 错误
spam( ham[ 1 ], { eggs: 2 } )
  • 在最后的括号和反括号之间
# 正确
foo = (0,)

# 错误
bar = (0, )
  • 逗号,分号和冒号之前
# 正确
if x == 4: print x, y; x, y = y, x

# 错误
if x == 4 : print x , y ; x , y = y , x
  • 但是,在切片操作中,冒号就像二元操作符,在其两侧应该有相同数量的空格(就像最低优先级的操作符一样)。在拓展的切片操作中,所有冒号应该有相同数量的空格。例外:当切片参数被省略时,空格也被省略
# 正确
ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
ham[lower:upper], ham[lower:upper:], ham[lower::step]
ham[lower+offset : upper+offset]
ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)]
ham[lower + offset : upper + offset]

# 错误
ham[lower + offset:upper + offset]
ham[1: 9], ham[1 :9], ham[1:9 :3]
ham[lower : : upper]
ham[ : upper]
  • 在调用方法的参数之前
# 正确
spam(1)

# 错误
spam (1)
  • 在索引和切片操作的左括号之前
# 正确
dct['key'] = lst[index]

# 错误
dct ['key'] = lst [index]
  • 在复制操作符前使用多个空格去对齐其他语句
# 正确
x = 1
y = 2
long_variable = 3

# 错误
x             = 1
y             = 2
long_variable = 3

其他推荐

  • 避免行尾的空格
  • 二元操作符两侧都只有一个空格,操作符包括:赋值(=),参数赋值(+=,-=等),比较 (==, <, >, !=, <>, <=, >=, in, not in, is, is not),布尔运算符(and, or, not)
  • 如果操作符有不同优先级,可以在最低优先级的操作符两侧加上空格。注意:不要使用操作一个空格,同时需要保证操作符两侧空格数量一致
# 正确
i = i + 1
submitted += 1
x = x*2 - 1
hypot2 = x*x + y*y
c = (a+b) * (a-b)

# 错误
i=i+1
submitted +=1
x = x * 2 - 1
hypot2 = x * x + y * y
c = (a + b) * (a - b)
  • 函数注解也遵循相同的规则,在箭头-> 两侧加上空格
  • 使用= 表示关键字参数或参数默认值时,不要加上空格
def complex(real, imag=0.0):
    return magic(r=real, i=imag)
  • 当联合参数注解和默认值时,需要在= 两侧加上空格
def munge(sep: AnyStr = None): ...
def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...
  • 混合语句(多个语句在同一行)是不鼓励的
# 正确
if foo == 'blah':
    do_blah_thing()
do_one()
do_two()
do_three()

# 不鼓励
if foo == 'blah': do_blah_thing()
do_one(); do_two(); do_three()
  • 有时候可以将短小的if/for/while 放在同一行,但是在多个分句的语句不要这么做。同时也要避免过长的行

何时使用行尾逗号

行尾逗号一般是可选的,除了表示单个元素的元组。为了清晰起见,建议使用括号将内容包起来

# 正确
FILES = ('setup.cfg',)

# 可行,但是令人迷惑
FILES = 'setup.cfg',

当使用版本控制系统时,如果列表,参数或引入的项随着时间会逐渐增长,使用行尾逗号是很有用的。这种模式就是每行一个值,同时加上行尾的逗号,在内容的最后一行的下一行加上结束括号。但是如果如果内容都在同一行就没有必要加上行尾逗号了。

# 正确
FILES = [
    'setup.cfg',
    'tox.ini',
    ]

# 错误
FILES = ['setup.cfg', 'tox.ini',]

注释

和代码违背的注释还不如没有。始终保证注释随着代码更新而更新

块注释

块注释一般情况是应用到注释后的代码上的,应该与代码保持相同层次的缩进。每一行的块注释应该以# 和一个空格开始(除非它是在注释内缩进对齐的)

块注释内的段落应该使用仅包含# 的行隔开

内联注释

尽量少用内联注释

内联注释是和语句在同一行的注释。内联注释和内容应该采用两个或以上个空格隔开。应该使用# 和一个空格开始

如果内联表示的内容很明显的话,那么内联注释是不必要的,甚至是令人迷惑的。

文档字符串

写出好的文档字符串的规范被记录在PEP 257

  • 为所有公开的模块,函数,类和方法写文档字符串。文档字符串对于非公开方法是不必要的,但是你应该使用注释描述此方法是什么。这个注释应该出现在def 的下一行
  • PEP 257 描述好的文档字符串。注意最重要的是,结束""" 应该独占一行
  • 对于单行的文档字符串,将结束""" 放在同一行

命名规范

Python库的命名规范有一点糟糕,我们一直都没能保持一致。然而,下面是当前推荐的命名规范。新的模块和库(包括第三方框架)应该按照这些标准写,但是已经存在的库如果之前采用其他风格,更推荐保持代码内部的一致性。

最重要的原则

作为公开API被用户可见的命名应该遵循命名表达用途而不是实现的原则

描述:命名风格

有很多种不同的命名风格。如果能够认出使用的是什么命名风格将会很有帮助,这和名字被用来做什么是独立的。

通常区分为下面的命名风格:

  • b (单个小写字母)
  • B (单个大写字母)
  • lowercase 小写字母
  • lower_case_with_underscores 带下划线的小写字母
  • UPPERCASE 大写字母
  • UPPER_CASE_WITH_UNDERSCORES 带下划线的大写字母
  • CapitalizedWords 首字母大写,类似驼峰
  • mixedCaseCapitalizedWords 区别在于首字母不大写
  • Capitalized_Words_With_Underscores 带下划线的驼峰,比较丑

还有一种风格是使用唯一的短前缀表示一组相关的命名。这个在Python中使用不多,但是为了完整性也在这边介绍一下。

X11库使用前缀X表示所有公开的函数。在Python中,这种风格是不必要的,因为属性和方法有对象的前缀,而方法名有模块名的前缀。

同时,要注意以下划线开头和结尾的特殊形式(这种风格可以与其他规范联合使用):

  • _single_leading_underscore 单下划线开头, 弱的内部引用标志,比如from M import * 不会导入下划线开头的对象
  • single_trailing_underscore_ 单下划线结束,使用这种方式避免和Python关键字冲突,比如:
Tkinter.Toplevel(master, class_='ClassName')
  • __double_leading_underscore 双下划线开头,此风格命名类属性会触发命名修饰(在类FooBar中,__boo 属性会修饰为_FooBar__boo
  • __double_leading_and_trailing_underscore__ 双下划线开头和结束,在用户命名空间内的魔术对象或属性,比如__init__, __import__, __file__. 不要发明这样的名字,按照文档使用

规范:命名规范

避免的名字

不要使用符号l (L的小写字母), O (大写字母O), I(i的大写字母) 作为单字符的变量名

在一些字体中,这些字体很难与0 或1进行区分。当尝试使用字符l 时,改成使用L 提醒替代

ASCII 兼容

在标准库中使用的标识符一定要是ASCII兼容的

包和模块名

模块应该使用短的,全小写的名字,如果可以提升可读性的话,可以在模块名中加入下划线。

Python的包也应该有短的,全小写的名字,但是使用下划线是不鼓励的。

当使用C,C++写的拓展模块有对应的Python模块提供更高层次的接口是间,C/C++的模块名应该使用下划线开头(比如_socket

类名

类名通常使用驼峰命名规范(首字母大写)

当接口有文档说明而且主要作为可调用对象时,也可以使用函数的命名规范

对于内建的名字有额外的规则:大部分内建的名字是单个单词(或两个一起的单词),而驼峰规则仅仅用于异常名和内建的常量

类型变量命名

PEP484 中引入的类型变量名通常应该使用简短的驼峰命名:T, AnyStr, Num。推荐增加后缀_co_contra 到用于表示协变或逆变行为的变量上。

from typing import TypeVar

VT_co = TypeVar('VT_co', covariant=True)
KT_contra = TypeVar('KT_contra', contravariant=True)
异常名

因为异常是类,类的命名规范可以应用在这里。但是,你应该在异常名上使用Error 后缀(如果这个异常确实是一个Error)

全局变量名

(在此之前,我们期待这个变量仅在同一个模块内使用。)这个规则和函数的规则类似。

被设计为通过from M import * 使用的模块应该使用使用__all__ 的机制去组织输出到全局,或者使用更老的下划线前缀的规则(这个方式用于表示此全局变量不是公开的)

函数和变量名

函数名应该是小写的,必要时采用下划线分隔单词提升可读性

变量名遵循与函数名相同的规则

混合大小写的情况仅仅用于当前代码已经是混合大小写的情况下(比如, threading.py),为了维持后续兼容性才使用

函数与方法参数

对实例方法通常使用self作为第一个参数

对类方法通常使用cls作为第一个参数

如果函数的参数与保留关键词冲突时,相对于使用缩写和拼写简化,更好的方法是在尾部添加下划线。因此class_class 更好。(也许使用同义词是一种更好地解决冲突的方法)

方法名和实例变量

使用 方法名的规则:小写字母以及单词通过必要的下划线分隔提升可读性

使用下划线开头用于表示非公开的方法和实例变量

为了避免和子类的命名冲突,使用双下划线去触发Python的命名修饰机制

Python使用类名修饰名字:如果类Foo有一个叫做__a 的属性,此不行不能通过Foo.__a 访问到(事实上,用户依旧可以通过Foo._Foo__a 访问到)。通常情况下,双下划线仅仅用于避免子类中的命名冲突。

注意:关于__names 的使用依旧存在一些争议(查看后续)。

常量

常量通常定义在模块级别而且都是大写字母并使用下划线分隔。比如MAX_OVERFLOWTOTAL

继承的设计

始终确定类的方法或实例变量(属性)应该是公开或非公开的。如果怀疑的话,选择非公开的;比起将公开属性设置为非公开的,将非公开属性设置为公开会更简单;

公开属性是你期待和你定义的类无关的客户使用的,并且确保不会出现向后不兼容的问题。非公开属性是不打算给第三方使用的;你保证非公开属性不会改变甚至是删除。

在这里我们没有使用‘’私有“,因为Python中没有真正私有的属性(这样设计省去大量不必要的工作)

另一类属性属于子类API的一部分(在其他语言中通常叫做”Protected”)。有些类被设计为用于继承,取决于类的行为进行拓展或修改。当设计这样的类时,需要谨慎地明确哪些属性时公开的,哪些是子类的一部分,以及哪些仅仅用在基类中。

考虑上面这些事情,下面是Pythonic代码风格的指南:

  • 公开属性不应该使用下划线开头;

  • 如果你的公开属性和保留关键字冲突了,在属性名尾部添加下划线。这比缩写和简写更好。(但是,和这条规则冲突的是,cls 对于类中变量和参数来说是一个更好的变量名,特别是对于变量名的第一个参数)

    注意1: 对于类方法,参考之前的参数名推荐

  • 对于简单的公开值属性,最好仅仅暴露属性名,而不是复杂的访问/修改方法。如果你发现简单的数据需要增加功能行为, 记住Python提供简单的方法用于功能增强。在这种情况下,使用Properties注解将功能实现隐藏在简单数据属性访问语法之后

    注意1:Properties注解仅仅对新风格类有用。

    注意2:尽量保证功能行为没有副作用,尽管缓存这种副作用看上去并没有什么大问题。

    注意3:避免对计算量大的操作使用Properties 注解,因为属性注解让调用者觉得计算量比较小。

  • 如果你的类用于被子类继承,而且你有不想被子类使用的属性,考虑使用双下划线开头而且没有尾部下划线的方式命名。这样会触发Python的命名修饰算法,将类名修饰到属性名中。这样可以避免属性名冲突,以免子类中包含相同的属性名。

    注意1:注意仅仅是将类名加入修饰名中,因此如果子类选择一个相同的类名和属性名,依旧会有命名冲突

    注意2:命名修饰可以用于特定的用途,比如调试和__getattr__() 不方便。但是命名修饰算法可以很好地记录,可以很好地执行。

    注意:不是所有人都喜欢命名修饰。试着去平衡意外命名冲突与高级调用者的潜在使用。

公开与内部接口

任意的向后兼容性仅仅适用于公开接口。因此,重要的是用户可以清晰地区分公开和内部接口。

文档化的接口被认为是公开的,除非文档清晰声明这些接口是保留或内部的接口,不保证后补兼容性。所有非文档化的接口都应该被任务是内部的。

为了更好地支持审视,模块应该使用__all__ 属性明确声明公开API的名字。设置__all__ 为空列表表明此模块没有公开API。

即使使用__all__ 合适地设置,内部接口(包,模块,类,方法,属性或其他名字)依旧应该使用单个下划线开头。

接口被认为是内部的如果包含的内部空间(包,模块或类)是内部的。

引用名应该总是被认为是实现细节。其他模块禁止依赖这些非直接访问的引用名,除非这些作为包含模块API的一部分被记录,比如os.path 或包中用于展示子模块功能的__init__ 模块。

编程建议

  • Python应该以不影响其他Python的实现(PyPy, Jython, IronPython, CPython, Psyco等)编写

比如,不要依赖CPython的高效实现a += ba = a +b 语句的原地字符串拼接。这种优化即使在CPython(仅仅在某些类型下工作)下也是脆弱的,而且不使用引用计数的实现中完全不存在优化。在性能敏感的库中,应该使用''.join() 进行替代。这种方式保证拼接在多种实现下都是线性时间的。

  • 与类似None的单例进行比较应该使用isis not,不要使用的相等操作符。

同时,小心地写if x 当真实的想要判断if is not None,比如,当测试一个默认值是None的变量或参数是否被设置了其他值。其他值可能是某种在布尔类型中可能会被当做false的类型(比如容器)。

  • 使用is not 操作符而不是not ... is 。两种操作符是功能上等价的,这种形式可读性更好,是首选。
# 正确
if foo is not None:
# 错误
if not foo is None
  • 当使用富操作实现排序操作时,最好是实现所有的六种操作(__eq__, __ne__, __lt__, __le__, __gt__, __ge__)而不是依赖其他代码仅仅实现特定的操作。

为了最小化付出的努力,functools.total_ordering() 装饰器提供工具生成缺失的比较方法

PEP 207表明Python假定了所有的反射规则。因此解释器可能会交换y > xx < yy >= xx <= y , 也许会交换参数x == yx != ysort()min() 方法保证使用使用< 操作符而max() 方法使用> 操作符。但是,最好去实现六种操作因此在其他情况下不会出现混淆。

  • 经常使用def 语句替代绑定了lambda语句的赋值语句
# 正确
def f(x): return 2*x
# 错误
f = lambda x: 2*x

第一种形式意味着结果方法对象的名字是明确的f 而不是一般的lambda. 通常情况下这种方式对异常追踪和字符串表示更有用。使用赋值语句消除了唯一好处lambda能提供而显示的def语句不能提供的。(比如可以嵌入更长的表达式中)

  • 异常类继承自Exception 而不是继承自BaseException, 直接继承自BaseException 是为Exception 类准备的,从BaseException 继承的异常被捕获基本都是错误的

设计异常层级应该基于代码可能需要捕获异常的不同之处,而不是基于异常的位置。目的是程序性地回答“出了什么问题”,而不是仅仅声明“一个问题出现了”(在PEP3151 查看一个内建异常层次的例子 )

类命名规范在这里也可以适用,如果异常是错误(error),你应该为异常类增加Error 前缀。用于非本地流控制或其他形式的信号的非错误异常不需要特别的前缀。

  • 合适地适用异常链。在Python 3中,“raise X from Y” 应该被用于表面显示的替代,而不会失去原始的追溯。

慎重地替代一个内部异常(在Python2中使用 “raise X” 或在Python3.3+中使用”rasie X from None”)时,保证相关的详情被转移到新的异常中(比如转换KeyError到AttributeError时保留属性名,或者在新的异常消息中嵌入原始异常的文本)

  • 在Python 2中抛出异常时,使用raise ValueError('message') 替代更老形式的rasie ValueError, 'message'

后一种形式是Python 3中不合法的语法

这种使用括号形式也意味着当异常参数参数长或包含字符串格式化时,由于括号你不需要使用续行字符。

  • 当捕获异常时,在可能的时候提到特定的异常而不是使用空的except: 语句
try:
    import platform_specific_module
except ImportError:
    platform_specific_module = None

空的except: 语句会捕获系统退出异常和键盘中断异常,这样让使用control-C终止程序变得更难,而且会掩饰其他问题。如果你想捕获所有的程序异常,使用except Exception: (空的except等价于except BaseException:

一个好的经验是将使用空的except: 语句情况限制到两种情况:

  1. 如果异常处理会打印或记录回溯信息;至少用户将会意识到错误发生了。
  2. 如通过代码需要进行一些清理工作,但是使用with.try...finally让异常向上传播应该是处理这种情况更好的方法。
  • 在给绑定的异常加一个名字时,最好使用Python 间.6中添加的明确的绑定语法名
try:
    process_data()
except Exception as exc:
    raise DataProcessingFailedError(str(exc))

这是在Python3中唯一支持语法,避免与就是基于逗号的语法产生二义性

  • 当捕捉到操作系统异常时,偏好Python3.3中引入的明确的异常层次,而不是errno值自省

  • 此外,对于所有的try/execpt语句,将try语句限制到必要的最小的代码量上。同样,这样可以避免屏蔽错误。

# 正确
try:
    value = collection[key]
except KeyError:
    return key_not_found(key)
else:
    return handle_value(value)

# 错误
try:
    # Too broad!
    return handle_value(collection[key])
except KeyError:
    # Will also catch KeyError raised by handle_value()
    return key_not_found(key)
  • 当资源局限于特定的代码,使用with语句去保证在使用之后资源被迅速和可靠地清理。这种情况下try/finally语句也是可接受的。

  • 当做一些获取和释放资源之外的事情时,上下文管理应该通过分离的方法触发

# 正确
with conn.begin_transaction():
    do_stuff_in_transaction(conn)

# 错误
with conn:
    do_stuff_in_transaction(conn)

后面这个例子没有提供信息表明__enter____exit__ 方法在事务处理之后在做关闭连接之外的事情。在这种情况下明确很重要。

  • 在返回值上保持一致性。要么函数中所有表达式返回表达式,要么都不返回表达式。如果其中的任一语句返回表达式,对于没有值要返回的地方应该通过return None 显示声明,而且显示的返回声明应该出现在函数的最后(如果可以执行到的话)
# 正确
def foo(x):
    if x >= 0:
        return math.sqrt(x)
    else:
        return None

def bar(x):
    if x < 0:
        return None
    return math.sqrt(x)

# 错误
def foo(x):
    if x >= 0:
        return math.sqrt(x)

def bar(x):
    if x < 0:
        return
    return math.sqrt(x)
  • 使用字符串方法替代字符串模块

字符串方法通过更快,而且与unicode 字符串共享相同的API。如果需要与Python2.0 之后的代码兼容,则覆盖此规则

  • 使用''.startWith()''.endsWith() 替代字符串切分去检查前缀或后缀

startWith()endsWith() 方法更清晰,更不容易出错:

# 正确
if foo.startswith('bar'):
# 错误
if foo[:3] == 'bar':
  • 对象类型比较应该使用instance() 方法而不是直接比较类型
# 正确
if isinstance(obj, int):
# 错误
if type(obj) is type(1):

当检查对象是否是字符串,记住也可能是unicode字符串。在Python 2中,str和unicode有一个共同的基类basestring, 因此你可以这样做:

if isinstance(obj, basestring):

注意在Python 3中,unicode和basestring不再存在(只有str),同时byte对象不再是string类型(被整数序列替代)。

  • 对于序列,(string , lists, tuple),使用空序列为false的事实
# 正确
if not seq:
if seq:

# 错误
if len(seq):
if not len(seq):
  • 不要写出依赖显式尾部空格的字符串字面量。这样的尾部空格视觉上不好区分的,而且某些编辑器(或最近的,reindent.py)会去除尾部空格
  • 不要使用==将布尔值与True和False进行比较
# 正确
if greeting:
# 不好
if greeting == True:
# 更差
if greeting is True:

函数注解

随着PEP484 被接受,函数注解的样式规则也改变了。

  • 为了向前兼容,在Python 3中的函数注解代码应该倾向使用PEP 484语法。(上一节中有一些注解的样式推荐)
  • 建议不再使用此文档早期版本中描述的试验性质的注解样式
  • 但是,在stdlib之外,在PEP484规则内的试验是被鼓励的。比如,使用PEP 484注解样式标记一个大的第三方库或应用,检视增加注解有多容易,并观察注解的存在是否增加代码的可理解性
  • Python 标准库在采用这些注解应该是保守的,但是对于新的代码或大的重构使用注解是被允许的。
  • 对于希望采用不同的方式来使用函数注解的代码,推荐在文件顶部添加这种形式的注释;
# type: ignore

这种注释告知类型检查忽略所有的注解。(在PEP484中可以找到更详细的方式去禁用类型检查的提醒)

  • 与Linters类似,类型检查应该是可选的,分离的工具。默认的Python解释器不应该由于类型检查发出任何消息,也不应该基于注解改变解释器的行为
  • 不想使用类型检查的用户可以忽略他们。但是,预期第三方库的用户会想要基于这些包运行类型检查。为此,PEP 484建议使用存根文件:.pyi文件,类型检查器优先于相应的.py文件读取这个文件。存根文件可以与库一起分发,也可以单独地(在库作者许可的情况下)通过Typeshed repo分发
  • 对于需要向后兼容的代码,类型注解应该以注释的形式添加。查看PEP 484相关的部分。

变量注解

PEP 526 引入了变量注解。对于变量注解的推荐样式类似于上面的函数注解:

  • 对于模块层的变量,类或实例变量,和局部变量的注解应该在冒号之后有一个空格
  • 在冒号之前应该没有空格
  • 如果复制有右侧的值,那么等号两侧都应该有一个空格
# 正确
code: int

class Point:
    coords: Tuple[int, int]
    label: str = '<unknown>'

# 错误
code:int  # No space after colon
code : int  # Space before colon

class Test:
    result: int=0  # No spaces around equality sign
  • 尽管PEP 526 在Python 3.6被接受,在所有版本的Python中对存根文件变量注解语法是更倾向的语法。(查看PEP 484查看详情)