Python – 代码规范
本 Python 代码编码规范参照 PEP8 所规定的代码规范。
这篇规范指南随着时间的推移而演变。
许多项目有自己的编码规范,在出现规范冲突时,项目自身的规范优先。
1、 前言
Guido 的一条重要的见解是代码阅读比写更加频繁。这里提供的指导原则主要用于提升代码的可读性,使得在大量的 Python 代码中保持一致。就像 PEP 20 提到的,“Readability counts”。
这是一份关于一致性的风格指南。这份风格指南的风格一致性是非常重要的。更重要的是项目的风格一致性。在一个模块或函数的风格一致性是最重要的。
然而,应该知道什么时候应该不一致,有时候编码规范的建议并不适用。当存在模棱两可的情况时,使用自己的判断。看看其他的示例再决定哪一种是最好的,不要羞于发问。
特别是不要为了遵守 PEP 约定而破坏兼容性!
几个很好的理由去忽略特定的规则:
- 当遵循这份指南之后代码的可读性变差,甚至是遵循 PEP 规范的人也觉得可读性差。
- 与周围的代码保持一致(也可能出于历史原因),尽管这也是清理他人混乱(真正的 Xtreme Programming 风格)的一个机会。
- 有问题的代码出现在发现编码规范之前,而且也没有充足的理由去修改他们。
- 当代码需要兼容不支持编码规范建议的老版本 Python。
2、代码布局
缩进
每一级缩进使用 4 个空格。
续行应该与其包裹元素对齐,要么使用圆括号、方括号和花括号内的隐式行链接来垂直对齐,要么使用挂行缩进对齐。当使用挂行缩进时,应考虑到第一行不应该有参数,以及使用缩进以区分自己时续行。
挂行缩进:在被括号括起来的语句中,左括号是这一行最后一个非空格字符,随后括号内的内容每一行进行缩进,直到遇到右括号。
制表符与空格
空格是首选的缩进方式。
制表符只能用于同样使用制表符缩进的代码保持一致。
Python3 不允许同时使用空格和制表符的缩进。
推荐
# 与左括号对齐
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)
# 挂行缩进应该再换一行
foo = long_function_name(
var_one, var_two,
var_three, var_four)
不推荐
# 没有使用垂直对齐时,禁止把参数放在第一行
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)
if
语句
另外,当 if
语句的条件部分长到需要换行写时,增加一个空格,再增加一个左括号来创造一个对齐缩进的多行条件。
# 在条件判断的语句添加额外的缩进
if (this_is_one_thing and
that_is_another_thing):
do_something()
# 或者
if (this_is_one_thing
and that_is_another_thing):
do_something()
多行结构
多行结构中的大括号、中括号和小括号的右括号可以单起一行,与多行结构的第一行第一个字符对齐,或者放在上一行的行尾
my_list = [
1, 2, 3,
4, 5, 6
]
# 或者
my_list = [
1, 2, 3,
4, 5, 6]
行的最大长度
所有行限制的最大字符数为 72
较长的代码行选择 Python 在小括号、中括号以及大括号中的隐式续行方式。通过小括号内表达式的换行方式将长串折成多行。这种方式应该优先使用。
反斜杠有时候仍然有用,比如比较长的多个 with
状态语句,或者 assert
语句
with open('/path/to/some/file/you/want/to/read') as file_1, \
open('/path/to/some/file/being/written', 'w') as file_2:
file_2.write(file_1.read())
assert x == 0, "x must be 0" \
", more infomation"
二元运算符换行
在使用运算符的时候如果有换行的需求,以往推荐的风格是在二元运算符之后中断,但这会影响可读性。原因有二:
- 操作符一般分布在屏幕上的不同列
- 每个运算符被移到了操作数的上一行
income = (gross_wages +
taxable_interest +
(dividends - qualified_dividends) -
ira_deduction -
student_loan_interest)
为了解决这种可读性的问题,遵循相反的约定:在二元运算符之前中断。
在Python中,允许在二元运算符之前或之后中断,只要代码约定一致。建议使用在二元运算符之前中断。
income = (gross_wages
+ taxable_interest
+ (dividends - qualified_dividends)
- ira_deduction
- student_loan_interest)
空行
顶层函数和类的定义,前后用两个空行隔开。
类里的方法定义用一个空行隔开。
相关的功能组可以用额外的空行隔开(谨慎使用)。
一堆相关的单行代码之间的空白行可以省略。
在函数中使用空行来区分逻辑段(谨慎使用)。
源文件编码
PEP8 中的描述为:对于 Python3 和更高的版本,Python 标准库中的所有标识符必须使用 ASCII 标识符,并在可行的情况下使用英文单词。此外,字符串文字和注释也必须是 ASCII。作者的名字如果不使用拉丁字母拼写,必须提供一个拉丁字母的音译。
导入
导入通常在分开的行。当然,在同一行导入也是被允许的。
导入总是位于文件的顶部,在模块注释和文档字符串之后,在模块的全局变量与常量之前。
导入应按照以下顺序分组,每组之间加入空行:
- 标准库导入
- 第三方库导入
- 本地库\包特定导入
# 推荐
import os
import sys
# 不推荐
import os, sys
# 可行
from subprocess import Popen, PIPE
# 分组
import os
import numpy as np
import tensorflow as tf
from mymodule import MyObject
推荐使用绝对路径导入,使用绝对路径可读性更佳并且性能更好。
然而,显式的指定相对导入路径是使用绝对路径的一个可接受的替代方案,特别是在处理使用绝对路径导入不必要冗长的复杂包布局时。
标准库要避免使用复杂的包引入结构,而总是使用绝对路径。
不应该使用隐式相对路径导入(注:在 Python3 中已删除隐式相对路径导入)
# 推荐
import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example
# 显式相对导入路径
from . import sibling
from .sibling import example
避免通配符的导入(from <module> import *
),因为这样做会不知道命名空间中存在哪些名字,会使得读取接口和许多自动化工具之间产生混淆。对于通配符的导入,有一个防御性的做法,即将内部接口重新发布为公共API的一部分。
当以这种方式重新发布名字时,以下关于公共和内部接口的准则仍然适用。
模块级的呆名
dunder name(呆名)即名字里面有两个前缀下划线和两个后缀下划线,例如 __all__
,__author__
,__version__
。这些呆名应该放在文档字符串的后面,以及除 from __future__ imports
之外的 import
表达式前面。Python 要求将来在模块中的导入,必须出现在除文档字符串之外的其他代码之前。
"""This is the example module.
This module does stuff.
"""
from __future__ import barry_as_FLUFL
__all__ = ['a', 'b', 'c']
__version__ = '0.1'
__author__ = 'Cardinal Biggles'
import os
import sys
3、字符串引号
在 Python 中,单引号和双引号字符串是相同的。PEP 不会为这个给出建议。可以自己定义一个规则并从一而终。当一个字符串包含单引号或双引号字符时,使用和最外层不同的符号来避免使用反斜杠(转义),从而提高可读性。
对于三引号字符串,总是使用双引号字符串。(与 PEP257 中的文档字符串约定保持一致)
4、表达式和语句中的空格
不能忍受的事情
在下列情况下,避免使用无关的空格:
-
紧跟在小括号,中括号或者大括号后
# 建议 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 munge(input: AnyStr): ... def munge() -> PosInt: ... # 不推荐 def munge(input:AnyStr): ... def munge()->PosInt: ...
-
在指定关键字参数或者默认参数值的时候,不要在
=
附近加上空格# 推荐 def complex(real, imag=0.0): return magic(r=real, i=imag) # 不推荐 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): ... # 不推荐 def munge(input: AnyStr=None): ... def munge(input: AnyStr, 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 foo == 'blah': do_blah_thing() for x in lst: total += x while t < 10: t = delay()
绝对别这样
if foo == 'blah': do_blah_thing() else: do_non_blah_thing() try: something() finally: cleanup() do_one(); do_two(); do_three(long, argument, list, like, this) if foo == 'blah': one(); two(); three()
唯一可以写到同一行的是
Ellipsis
,即...
def foo(): ...
5、何时使用尾随逗号
尾随逗号通常是可选的。但当你需要创建只有一个元素的元组是,它是必填项。
# 正确
FILES = ('setup.cfg',)
# 错误
FILES = 'setup.cfg',
当尾随逗号是冗余的时候,在使用版本控制系统(VCS)时如果值随着时间的推移而扩展时,会非常有用。
使用的方式是将每个值本身放在一行,始终添加尾随逗号,并在下一行添加右括号。但是,与结束分隔符在同一行上使用尾随逗号没有意义。
# 建议
FILES = [
'setup.cfg',
'tox.ini',
]
initialize(FILES,
error=True,
)
# 不建议
FILES = ['setup.cfg', 'tox.ini',]
initialize(FILES, error=True,)
6、注释
与代码相矛盾的注释比没有注释还糟!当代码更改时,优先更新对应的注释!
注释应该是完整的句子。如果一个注释是一个短语或句子,它的第一个单词应该大写,除非它是以小写字母开头的标识符。
如果注释很短,结尾的句号可以省略。
块注释一般由完整句子的一个或多个段落组成,并且每句话结束有个句号
在句尾结束的时候应该使用两个空格
当用英文书写时,遵循 Strunk and White 的书写风格。
在非英语国家的 Python 程序员,请使用英文写注释,除非你 120% 确定你的代码不会被使用其他语言的人使用。
如果不使用英文写注释,请注意不要混用语言,要么纯英文,要么纯某种语言。
块注释
块注释通常适用于跟随它们的某些(或全部)代码,并缩进到与代码相同的级别。块注释的每一行开头使用一个 #
和一个空格(除非块注释内部缩进文本)。
块注释内部的段落通过只有一个 #
的空行分隔
可以使用 CodeTags
,推荐使用 TODO
和 FIXME
行内注释
有节制地使用行内注释。
行内注释是与代码语句同行的注释。
行内注释与代码至少要有两个空格分隔。
注释由一个 #
和一个空格开始。
如果代码非常明显,行内注释是不必要的。
# 不必要的
x = x + 1 # Increment x
# 有用的
x = x + 1 # Compensate for border
文档字符串
编写好的文档说明(也叫 Docstrings)的约定在 PEP257 中
-
要为所有的公共模块、函数、类以及方法编写文档说明
-
非公共的方法没有必要,但是应该有一个描述方法具体作用的注释。这个注释应该在
def
那一行之后。 -
PEP257 描述了写出好的文档说明相关的约定。特别需要注意的是,多行文档说明使用的结尾三引号应该自成一行
"""Return a foobang Optional plotz says to frobnicate the bizbaz first. """
-
对于单行的文档说明,尾部的三引号应该和文档同一行
"""Single Line Docstrings"""
7、命名规范
Python 库的命名规范很乱,从来没能做到完全一致
推荐使用以下的命名规范
最重要的原则
暴露给用户的API接口的命名,应该遵循反映使用场景而不是实现的原则
描述:命名风格
b
(单个小写字母)B
(单个大写字母)lowercase
lower_case_with_underscores
UPPERCASE
UPPER_CASE_WITH_UNDERSCORES
CapitalizedWords
- 注:当首字母大写的风格中用到缩写时,所有缩写的字母用大写
- 例如:
HTTPServerError
比HttpServerError
要好
8、约定俗成:命名约定
应避免的名字
- 尽量避免使用
l
(小写的 L)、O
(大写的 O)或者I
(大写的 I)作为单字符变量名 - 在某些字体里,这些字符与 1 和 0 的区分度比较低
- 注:如果确保整个团队都使用区分度比较大的等距字体,也可以使用
包名和模块名
- 模块应该用简短全小写的名字,如果为了提高可读性,下划线也是可以用的(尽量避免)
- Python 包名也应该使用简短全小写的名字,但不建议用下划线
- 当使用 C 或者 CPP 编写了一个依赖于提供高级接口的 Python 模块的扩展模块,这个 C/CPP 模块需要一个下划线前缀,如
_socket
类名
- 类名一般使用首字母大写的约定。
- 在接口被文档化并且主要被用于调用的情况下,可以使用函数的命名风格替代
- 推荐:使用
CapitalizedWords
命名风格
类型变量名
- PEP484 中引入的类型变量名称通常应使用
CapWords
前缀短名字,如:T
、AnyStr
、Num
。 - 建议在用于相应的声明中添加
_co
或_contra
到变量来使用。
from typing import TypeVar
VT_co = TypeVar('VT_co', covariant=True)
KT_contra = TypeVar('KT_contra', contravariant=True)
异常名
- 因为一般来说异常都是类,所以类的命名方法在这里也适用。
- 不过,需要在异常类的名字后面加上
Error
后缀。
全局变量名
- 约定和函数命名规则一致
- 通过
from <module> import *
导入的模块应使用__all__
来防止全局变量对外暴露,或者使用在全局变量前加下划线的方式
函数名及变量名
- 函数名和变量名应该采用
lower_case_with_underscores
风格 - 大小写混合仅在为了兼容原来主要以大小写混合风格的情况下使用(如 threading.py),保持向后兼容性
函数和方法参数
- 始终要将
self
作为实例方法的第一个参数 - 始终要将
cls
作为类方法的第一个参数 - 如果函数的参数名和已有的关键词冲突,在最后加单下划线比缩写或者随意拼写更好,如:
class_
比clss
要好
方法名和属性
-
命名风格与函数名及变量名一致
-
protected
方法和属性前使用单下划线 -
private
方法和属性前使用双下划线-
Python 中对于加了双下划线的实例方法和属性,进行了命名转换,例如
class Foo: def __init__(): self.__name = "Bar" foo = Foo() print(foo.__name) # 报错 print(foo._Foo__name) # 成功
-
常量
- 常量通常定义在模块中,并且使用
UPPER_CASE_WITH_UNDERSCORES
风格 - 例如:
LEVEL
、MAX_OVERFLOW
继承的设计
- 始终要考虑到一个类的方法和属性应该是共有还是非公有。如果存在疑问,那就选非公有,因为将一个非公有变量转为公有比反过来容易
- 公共属性是那些与类无关的、用户使用的属性,并承诺避免做出向后不兼容的更改。非公共属性是那些不打算让第三方使用的属性,不需要承诺非公有属性不会被修改或被删除
- 我们不使用
private
(私有)这个说法,因为在 Python 中并没有真正的私有属性
以下是 Pythonic 的准则
- 公共属性不应该有前缀下划线
- 如果公共属性名和关键字冲突,请在属性名附加单个尾随下划线。这比缩写和随意拼写好很多
- 对于单一的公有数据属性,最好直接对外暴露它的变量名,而不是通过负责的存取器(accessor)或突变(mutator)方法
- 如果类打算用来继承,并且这个类有不希望子类使用的属性,就要考虑使用双下划线前缀并且没有后缀下划线的命名方式
9、公共和内部的接口
任何向后兼容保证只适用于公共接口(API)
因此,用户清晰地区分公共接口和内部接口非常重要
文档化的接口被认为是公开的,除非文档明确声明它们是临时或内部接口,不受通常的向后兼容性保证。所有未记录的接口都应该是内部的
为了更好地支持内省(introspection),模块应该使用 __all__
属性显式地在它们的公共API中声明名称。将 __all__
设置为空列表表示模块没有公共API
即使通过 __all__
设置过,内部接口依然需要单个下划线前缀
如果一个命名空间(包、模块、类)被认为是内部的,那么包含它的接口也应该被认为是内部的
导入的名称应该始终被视作是一个实现的细节,其他模块必须不能间接访问这样的名称,除非它是包含它的模块中有明确的文档说明的 API,如 os.path
或者是从一个包里从子模块公开函数接口的 __init__
模块。
10、编程建议
-
代码应该用不损害其他 Python 实现的方法去编写(Pypy、Jython、CPython 等)
比如,不要依赖于在CPython中高效的内置字符链接语句a += b
。这种优化甚至在 CPython 中都是脆弱的(只适用于某些类型),并且没有出现在不使用引用计数的实现中。在性能要求比较高的库中,可以用"".join()
来代替。 -
像
None
这样的单例对象的比较应该始终用is
或is not
,而不要使用==
-
使用
is not
运算符,而不是not ... is
。虽然两者在功能上完全相同,但是前者更易于阅读,所以优先考虑# 推荐 if foo is not None: ... # 不推荐 if not foo is None: ...
-
当使用富比较(rich comparisons)实现排序操作时,最好实现全部的六个操作符(
__eq__, __ne__, __lt__, __le__, __gt__, __ge__
)
为最大程度减少这一过程的开销,functools.total_ordering()
装饰器提供了用于生成缺少的比较方法的工具。
PEP 207 指出Python实现了反射机制。因此,解析器会将y > x
转变为x < y
,将y >= x
转变为x <= y
,也会转换x == y
和x != y
的参数。sort()
和min()
方法确保使用<
操作符,max()
使用>
操作符。然而,最好还是实现全部六个操作符,以免在其他地方出现冲突。 -
始终使用
def
表达式,而不是通过赋值语句将lambda
表达式绑定到一个变量上。# 推荐 def f(x): return 2*x # 不推荐 f = lambda x: 2*x
第一个形式意味着生成的函数对象名称是
f
而不是泛型<lambda>
,这在回溯和字符串显示的时候更有用。赋值语句的使用消除了lambda
表达式优于显式def
表达式的唯一优势。 -
从
Exception
继承异常,而不是从BaseException
。直接继承BaseException
的异常适用于几乎不用来捕捉的异常。 -
适当地使用异常链接。在 Python3 中,为了不丢失原始的根源,可以显式地指定
raise X from Y
作为替代。 -
在 Python2 中抛出异常时,使用
raise ValueError("message")
而不是更老的形式raise ValueError, "message"
-
当捕获到异常时,如果可以的话写上具体的异常名,而不是只用一个 except 块
try: import platform_specific_module except ImportError: platform_specific_module = None
如果只有一个 except 块,将会捕获到
SystemExit
和KeyboardInterrupt
异常,这样会很难通过Control-C
中断程序,而且会掩盖掉其他问题。如果想捕获所有异常,显式地使用raise Exception
(只有 except 等价于except BaseException
)。- 两种情况不应该只使用 except 块
- 1、如果异常处理的代码会打印或者记录 log,至少让用户知道发生了一个错误
- 如果代码需要做清理工作,使用
raise...try...finally
能很好处理这种情况并且能让异常继续上浮。
-
当给捕捉到的异常绑定一个名字时,推荐使用在 Python2.6 中加入的显式命名绑定语法:
try: process_data() except Exception as e: raise DataProcessingFailedError(str(e))
为了避免与原来基于逗号分隔的语法出现歧义,Python3 只支持这种语法
-
当捕捉操作系统的错误时,推荐使用 Python3.3 中
errno
内定数值指定的异常等级 -
另外,对于所有的
try...except
语句块,在try
语句中只填充必要的代码,这样可以避免掩盖掉 bug。# 推荐 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)
-
返回的语句保持一致,函数中的返回语句都应该返回一个表达式,或者都不返回
如果一个返回语句需要返回一个表达式,那么在没有值可以返回的情况下,需要用return None
显式指明,并且在函数的最后,显式指定一条返回语句。 -
使用字符串方法代替字符串模块
字符串方法总是更快,并且和unicode
字符串分享相同的 API -
使用
.startwith()
和.endwith()
代替通过字符串切割的方法去检测前缀和后缀
这两个函数更干净,出错几率更小# 推荐 if foo.startswith('bar'): ... # 不推荐 if foo[:3] == 'bar': ...
-
对象类型的比较应该用
isinstance()
而不是直接比较type
# 推荐 if isinstance(obj, int): ... # 不推荐 if type(obj) is type(1): ...
-
空序列为
False
# 推荐 if not seq: if seq: # 不推荐 if len(seq): if not len(seq):
-
不要编写依赖于大量尾随空格的字符串文本,这种尾随空白在视觉上无法区分。一些编辑器会自动去掉它们
-
不要用
==, is, is not
去和True
或者False
比较# 推荐 if greeting: ... # 不推荐 if greeting == True: ... # 更糟 if greeting is True: ...
-
在使用
try...finally
的时候,请避免将流程控制语句return, break, continue
放在finally
语句块中。这是因为在finally
语句执行时隐式取消任何异常。# 不推荐 def foo(): try: 1 / 0 finally: return 42
11、函数注释
随着 PEP484 的引入,函数注释的风格规范有些变化
-
为了向前兼容,在 Python3 代码中的函数注释应使用 PEP484 的语法规则
-
不在鼓励使用之前在 PEP 中推荐的实验式样式
-
Python 的标准库代码应该保守使用这种注释,但新的代码或者大型的重构可以使用这种注释
-
如果代码希望对功能注释有不同的用途,建议在文件的顶部增加一个这种形式的注释
# type: ignore
这会告诉检查器忽略所有的注释。
-
像 linters 一样,类型检测器是可选的可独立的工具。默认情况下,Python 解释器不应该因为类型检查而发出任何消息,也不应该基于注释改变它们的行为。
-
不想使用类型检测的用户可以忽略他们。然而,第三方库的用户可能希望在这些库上运行类型检测。为此,PEP484 建议使用存根文件类型:.pyi 文件,这种文件类型相比于 .py 文件会被类型检测器读取。存根文件可以和库一起,或者通过 typeshed repo 独立发布(通过库作者的许可)
-
对于需要向后兼容的代码,可以以注释的形式添加函数注释。
12、变量注释
PEP526 引入了变量注释,样式建议与上述函数注释上的样式建议类似
-
模块级变量、类和属性以及局部变量的注释在冒号之后应该具有单个空格
-
冒号前应没有空格
-
如果赋值具有右侧,则
=
的两则应只有一个空格# 推荐 code: int class Point: coords: Tuple[int, int] label: str = '
' # 错误 code:int # No space after colon code : int # Space before colon class Test: result: int=0 # No spaces around equality sign -
尽管 PEP526 是在 Python3.6 的时候接受的,但是变量注释语法是所有版本的 Python 上存根文件的首选语法
13、其他规范
这些规范并非强制,仅为建议
-
在每个包里面最好有一个
__init__.py
文件,虽然这在 Python3 之后并非必须。这一文件能够有效地管理包 -
尽量只使用简单的列表/字典/集合推导式
过于复杂的推导式只会降低代码的可读性和可维护性 -
if...else...
尽量不超过3层,如果超过,尽量考虑转化为if...elif...else...
,或者使用设计模式 -
定义对象、变量、新建数组等涉及空间的操作尽量在循环体外部执行,以降低资源消耗
-
避免出现重复代码,即 DRY 原则(Don't Repeat Yourself)
-
不使用的变量可以用单下划线
_
来接收,比如for _ in range(5): print("Hello World")
-
不推荐使用
;
-
格式化字符串统一使用
f-string
,它比旧的写法性能更高,可读性也更好。
* 参考文章: