Python是一门很自由的语言。很多场景下都没有很严格的限制,只有一些推荐的规范。但也正因为如此,我们更应该遵循标准规范,这样才能写出良好的代码。其中,在编码风格方面,有一个专门的PEP(Python Enhancement Proposal,Python增强提案)是针对代码风格的指南。我们在编写代码的时候要尽量遵循这个规范。

PEP8 规范

空格与缩进的使用

  • 用空格来表示缩进,而不要用制表符(Tab)。最好是4个空格。注意:空格和Tab不能混用,否则会报错。当然你可能会说,我就是混用的,为什么没出过错。这是因为IDE会自动帮你把Tab转成4个空格。
  • 和语法相关的缩进都用四个空格表示。(下文将四个空格写为一级缩进)
  • 每行的字符数不超过120个字符。太长的行需要进行换行,换行后的新代码在原缩进的基础上再增加一级缩进。
  • 函数和类的定义之间,代码前后用两行空行分隔。
  • 同一个类中,各个方法之间用一行空行分隔。
  • 二元运算符(如+、-、*、/、=)左右两边各保留一个空格。
  • 文件的结尾留一行空行。
  • 注释中#的后面应该空一格。如果注释前有代码,#前应至少空两个空格。

标识符命名

  • 变量、函数和属性使用小写字母。多个单词之间用下划线_分隔。

  • 类中受保护的实例属性,以一个下划线开头,如_protected = 1。在类以外,你可以直接调用单下划线开头的属性,但并不推荐这样做。因为这种写法表示作者并不希望其他人调用这个属性,并且以后作者可能会对这个属性本身以及相关的方法进行修改,从而导致后续维护时出现意料之外的问题。

  • 类中私有的实例属性,以两个下划线开头,如__protected = 1。注意,在类之外,你无法直接调用双下划线开头的属性,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class PrivateClass:
    def __init__(self):
    self.__private = 'I am private'

    def get_private(self):
    return self.__private


    if __name__ == '__main__':
    private = PrivateClass()

    print(private.get_private())

    >>>
    I am private

    print(private.__private)
    >>>
    Traceback (most recent call last):
    File "XXX.py", line 12, in <module>
    print(private.__private)
    AttributeError: 'PrivateClass' object has no attribute '__private'. Did you mean: 'get_private'?

    但是,并不是说就一定无法调用。你可以通过以下方式强行调用:

    1
    print(private._PrivateClass__private)

    但这种方法极其不推荐。紧急按钮外面的保护盒是为了防止意外按下,但并不是为了防止故意按下。不要在类外调用双下划线开头的私有属性。

  • 类和异常的命名为每个单词的首字母大写(即驼峰命名法)。

  • 模块级别的常量,应该用全大写字母,如果有多个单词则用下划线分隔。

  • 类的实例方法,应该把第一个参数命名为self表示实例自身。(你会发现不叫self依然可以运行,但规范还是应该遵守。)

  • 类的类方法,应该把第一个参数命名为cls表示类自身。

  • 返回的用不到的变量应该用下划线表示,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    a_list = ['a' for _ in range(5)]


    def test():
    return 'b', 'c'


    b, _ = test()

表达式和语句

  • 采用内联形式的否定词,而不要把否定词放在整个表达式的前面。

    1
    2
    3
    4
    #应该是
    if a is not b:
    #而不是
    if not a is b:
  • 不要用检查长度的方式来判断字符串、列表等是否为None或者没有元素,应该用if not x这样的写法来检查它。

  • 就算if分支、for循环、except异常捕获等中只有一行代码,也不要将代码和if、for、except等写在一起,分开写才会让代码更清晰。

  • import语句总是放在文件开头的地方。

  • 引入模块的时候,from math import sqrtimport math更好。

  • 如果有多个import语句,应该将其分为三部分,从上到下分别是Python标准模块、第三方模块和自定义模块,每个部分内部应该按照模块名称的字母表顺序来排列。

类型提示

如果时间充裕,或者编写需要给其他人看的代码,最好在代码中加入类型提示。

1
2
def num2str(num: int) -> str:
return str(num)

参数冒号后面的类型表明num应该是int类型,而->后面的类型表示这个函数会返回一个str类型的返回值。注意,有些类型需要导入typing才能正确表示,如列表List:

1
2
3
4
5
from typing import List


def num_list2str(num_list: List[int]) -> tuple[str, List[str]]:
return str(num_list), [str(num) for num in num_list]

另外,如果函数有多种允许的参数类型或者有多种可能的返回值,用Union表示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from typing import Union


def num2str(num: Union[int, float]) -> Union[str, None]:
if isinstance(num, (int, float)):
return str(num)
else:
return None


# 或者用|符号
def num2str(num: int | float) -> str | None:
if isinstance(num, (int, float)):
return str(num)
else:
return None

其他一些比较特殊的类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from typing import Any, Type, Callable, Iterable


def callback(function: Type[Callable]) -> Any:
v = function()
return v


def dict_value2iter(dic: dict[str, Any], to_type: str) -> Iterable[str] | None:
if to_type == 'list':
return [v for v in dic.values()]
elif to_type == 'tuple':
return tuple(v for v in dic.values())
elif to_type == 'set':
return {v for v in dic.values()}
else:
return None