如何使用PEP8写出漂亮的Python代码

PEP 8是关于如何编写Python代码的指南和最佳实践的文档,由Guido van Rossum、Barry Warsaw和Alyssa Coghlan于2001年撰写,其主要关注点是提高Python代码的可读性和一致性。

本教程将介绍:

  • 编写符合PEP 8的Python代码

  • 理解PEP 8中所列出的指导方针背后的理由

  • 设置你的开发环境,以便你可以开始编写符合PEP 8的Python代码

PEP 代表 Python 增强提案,有很多 PEP。这些文档主要描述了为 Python 语言提出的新特性,但一些 PEP 也专注于设计和风格,旨在为社区提供资源。PEP 8 是这些以风格为重点的 PEP 之一。

在本教程中,你将学习 PEP 8 中列出的关键指导方针,你将探索初学者到中级编程主题,你可以通过阅读完整的 PEP 8 文档来学习更高级的主题。

为什么我们需要PEP 8

“Readability counts.”

— The Zen of Python

PEP 8 旨在提高 Python 代码的可读性。但是可读性为什么如此重要?为什么根据 Python 的哲学,编写可读的代码是 Python 语言的指导原则之一?

注意:当 Python 社区指代遵循 Python 特定的习惯写作风格的代码时,你可能会遇到术语 Pythonic。Pythonic 代码遵循 Python 的设计原则和哲学,并强调可读性、简单性和清晰性。

正如Guido van Rossum所说:“代码被阅读的次数远远多于编写的次数。”你可能花费几分钟,甚至一整天的时间来编写一段代码来处理用户身份验证。一旦你编写了它,你就再也不会编写它了。

但是你肯定会再读一遍。那段代码可能仍然是你正在做的项目的一部分。每次你回头查看那个文件,你都必须记住那段代码是做什么的,以及你为什么要写它,所以可读性很重要。

在你写完一段代码几天或几周后,你可能很难记住它做了什么。

如果你遵循PEP 8,你可以确定你已经很好地命名了变量。你会知道你已经添加了足够的空格,这样更容易在代码中遵循逻辑步骤。你也会很好地注释你的代码。所有这些都意味着你的代码更具可读性,更容易重用。如果你是一个初学者,遵循PEP 8的规则可以使学习Python成为一项更愉快的任务。

注意:如果你正在寻找一份开发工作,遵循PEP 8是特别重要的。编写清晰、可读的代码显示了专业性。它会告诉雇主,你知道如何很好地组织你的代码。

如果你有更多的编写 Python 代码的经验,那么你可能需要与他人合作。在这里,编写可读的代码至关重要。其他人可能从未见过你或见过你的编码风格,他们将不得不阅读和理解你的代码。拥有你遵循和认可的指导方针将使其他人更容易阅读你的代码。

命名约定

“Explicit is better than implicit.”

— The Zen of Python

当你编写Python代码时,你需要命名很多东西:变量、函数、类、包等等。选择合理的名称将节省你以后的时间和精力。你将能够从名称中找出某个变量、函数或类代表什么。你还将避免使用可能导致错误的令人困惑的名称,这可能会导致错误难以调试。

一个建议是永远不要使用  l 、 O  或  I  单字母名称,因为这些可能被误认为是  1  和  0 ,这取决于程序员使用的字体。

例如,考虑下面的代码,其中你将值  2  赋给单个字母  O :

这样做看起来就像你试图将 2 重新赋值为0,然而在Python中这样做是不可能的,会导致语法错误,使用一个模棱两可的变量名,如 O ,会使你的代码更混乱,更难阅读和推理。

命名样式

下表列出了 Python 代码中一些常见的命名风格以及应该在何时使用它们:

类型

命名约定

例子

函数

使用小写字母。用下划线分隔单词以提高可读性。

function, python_function

变量

使用小写字母、单词或词组。用下划线分隔单词以提高可读性。

x, var, python_variable

每个单词的开头字母都大写,不要用下划线分隔单词,这种格式叫做驼峰式或帕斯卡式。

Model, PythonClass

方法

使用小写字母。用下划线分隔单词以提高可读性。

class_method, method

常数

使用大写字母、单词或词组。用下划线分隔单词以提高可读性。

CONSTANT, PYTHON_CONSTANT, PYTHON_LONG_CONSTANT

模块

使用短小、小写的单词或词组。用下划线分隔单词以提高可读性。

module.py, python_module.py

使用短小、小写的单词或词组。不要用下划线分隔单词。

package, pythonpackage

这些是一些常见的命名约定和如何使用它们的示例。但为了编写可读的代码,你仍然需要仔细选择字母和单词。除了在代码中选择正确的命名风格外,你还必须仔细选择名称。下面是一些关于如何尽可能有效地做到这一点的提示。

如何选择名字

为变量、函数、类等选择名字可能具有挑战性。在编写代码时,应该在命名选择上花费相当多的精力,因为这将使代码更具可读性。在 Python 中命名对象的最佳方法是使用描述性名称,以便清楚地说明对象代表什么。

在命名变量时,你可能倾向于选择简单的、单个字母的小写名称,如  x 。但是除非你使用  x  作为数学函数的参数,否则  x  代表什么并不清楚。想象一下,你正在将一个人的名字存储为字符串,并希望使用字符串切片以不同的格式格式化他们的名字。你可能会得到这样的结果:

❌ Not recommended

>>> x = "John Smith"
>>> y, z = x.split()
>>> print(f"{z}, {y}")
'Smith, John'

这会起作用,但是你必须跟踪 x , y 和 z 代表什么,这可能也会让合作者感到困惑,更清晰的命名选择应该是这样的:

✅ Recommended

>>> name = "John Smith"
>>> first_name, last_name = name.split()
>>> print(f"{last_name}, {first_name}")
'Smith, John'

同样,为了减少输入的字符量,在选择名称时使用缩写可能很有吸引力。在下面的示例中,定义了一个  db()  函数,它接受一个参数  x ,并将其翻倍:

❌ Not recommended

def db(x):
    return x * 2

乍一看,这似乎是一个明智的选择, db()  的名字可能是 double 的缩写,但是想象一下,几天后再回头看这段代码,你可能已经忘记了这个函数的目的,这会使你很难猜出你是如何缩写它的。

下面的例子更加清晰,如果你在写完这段代码几天后再回头看,你仍然能够读懂这个函数的用途:

✅ Recommended

def multiply_by_two(x):
    return x * 2

同样的道理也适用于 Python 中的所有其他数据类型和对象,总是尽可能使用最简洁但最具描述性的名称。

代码布局

“Beautiful is better than ugly.”

— The Zen of Python

代码的布局对代码的可读性有很大的影响。在本节中,你将学习如何添加垂直空格来提高代码的可读性。你还将学习如何处理PEP 8中推荐的79个字符的行限制。

空白行

垂直空格,或空白行,可以极大地提高代码的可读性。捆绑在一起的代码可能会让人难以阅读。同样,代码中太多的空白行会使它看起来非常稀疏,读者可能需要滚动更多次。下面是关于如何使用垂直空格的三个关键指导原则。

用两行空格将顶级函数和类括起来,顶级函数和类应该相当自成一体,处理单独的功能,在它们周围多留一些垂直空间,这样可以清楚地表明它们是独立的:

class FirstClass:
    pass


class SecondClass:
    pass


def top_level_function():
    return None

因此,PEP 8 建议在顶级函数和类定义周围使用两行空格。

类中的方法定义之间用单行空格隔开,类中的方法之间都是相互关联的,最好只用一行空格隔开:

class ClassWithMethods:
    def first_method(self):
        return None

    def second_method(self):
        return None

在代码示例中,你可以看到一个类定义,其中包含两个实例方法,它们之间用一个空行分隔。

在函数内部适当使用空行来表示清晰的步骤。有时,一个复杂的函数在  return  语句之前需要完成几个步骤。为了帮助读者理解函数内部的逻辑,可以在每个逻辑步骤之间留出空行。

下面的例子中,有一个函数计算列表的方差,这是一个两步问题,所以你可以在两个步骤之间留空行来表示:

def calculate_variance(numbers):
    sum_numbers = 0
    for number in numbers:
        sum_numbers = sum_numbers + number
    mean = sum_numbers / len(numbers)

    sum_squares = 0
    for number in numbers:
        sum_squares = sum_squares + number**2
    mean_squares = sum_squares / len(numbers)

    return mean_squares - mean**2

在这个代码示例中,你使用空行将逻辑步骤分开以提高可读性。在  return  语句之前也有一个空行。这有助于读者清楚地看到函数返回的内容。

如果你仔细使用垂直空格,它可以极大地提高代码的可读性,帮助读者直观地理解代码是如何划分成各个部分的,以及这些部分之间是如何相互关联的。

最大行长和断行

PEP 8 建议行应该限制在 79 个字符以内。这允许你有多个文件相邻打开,同时还避免了行包装。

当然,保持语句不超过 79 个字符并不总是可能的,因此,PEP 8 还概述了允许语句在多行中运行的方法。

如果代码包含在圆括号、方括号或大括号中,Python 会假定行继续:

def function(arg_one, arg_two,
             arg_three, arg_four):
    return arg_one

在这个例子中,你将 arg_three 和 arg_four 移动到新行,缩进与第一个参数相同的级别。你可以像这样拆分你的代码,因为Python的隐式行连接在括号内。

如果不能使用隐式延续,那么可以使用反斜杠( \ )来断行:

from package import example1, \
    example2, example3

然而,任何时候,只要可以使用隐式延续,那么你应该优先使用它而不是反斜杠。

如果你需要在二进制操作符周围换行,比如  +  和  * ,那么你应该在操作符之前换行。这个规则源于数学。数学家们认为在二进制操作符之前换行可以提高可读性。比较下面两个例子。

下面是一个在二进制操作符前进行 breaking 的例子:


✅ Recommended

total = (first_variable
         + second_variable
         - third_variable)

你能立即看到Python将要加或减哪个变量,因为运算符就在它要操作的变量旁边。

现在,这里有一个在二进制操作符之后进行断点的例子:

❌ Not recommended

total = (first_variable +
         second_variable -
         third_variable)

在这里,很难看出 Python 添加了哪个变量,又减去了哪个变量。

在二进制运算符前断行可以产生更可读的代码,所以PEP 8鼓励这样做。在二进制运算符后断行的代码仍然符合PEP 8。然而,我们鼓励你在二进制运算符前断行。

缩进

“There should be one—and preferably only one—obvious way to do it.”

— The Zen of Python

缩进,或者说前导空格,在 Python 中极为重要,Python 代码行中的缩进级别决定了 Python 如何将语句分组在一起。

考虑以下例子:

x = 3
if x > 5:
    print("x is larger than 5")

对  print()  的缩进调用让 Python 知道,只有当  if  语句返回  True  时才应该执行这一行。同样的缩进也用于在调用函数或指定类时告诉 Python 执行什么代码。

PEP 8 中的关键缩进规则如下:

  • 使用连续四个空格来表示缩进。

  • 使用空格而不是制表符。

虽然 Python 代码可以使用任何数量的一致缩进,但四个空格是 Python 社区中广泛使用的约定,你也应该坚持它。

制表符vs空格

如上所述,在缩进代码时,应该使用空格而不是制表符。你可以调整文本编辑器的设置,以便在按 Tab 键时输出四个空格而不是制表符。

Python 3不允许混合使用制表符和空格,写下下面的代码,确保在点号( · )和制表符( ⇥ )符号处使用空格:

mixed_indentation.py

1def mixed_indentation(greet=True):
 2····if greet:
 3········print("Hello")
 4⇥   print("World")  # Indented with a tab.
 5
 6mixed_indentation()

当你只查看代码时,区别是看不见的,但是请确保在第 4 行中使用 tab 字符进行缩进,而对于其他缩进使用四个空格字符。

如果你使用 Python 3 并运行混合了制表符和空格的代码,那么你将得到一个错误:

$ python mixed_indentation.py
File "./mixed_indentation.py", line 4
print("World")  # Indented with a tab.
TabError: inconsistent use of tabs and spaces in indentation

你可以用制表符或空格来表示缩进,但是如果你使用的是Python 3,那么你必须选择一致的缩进方式,否则你的代码将无法运行。

注意:如果你混合使用制表符和空格,Legacy Python 2 不会抛出错误。这可能会导致模棱两可的情况,这取决于文本编辑器如何解释制表符字符。

如果你有兴趣尝试一下,那么你可以使用 pyenv 安装 Python 2 版本,如果你用 Python 2 运行  mixed_indentation.py ,那么你不会看到错误消息,除非你使用  -t  或  -tt  选项。

PEP 8 建议你始终使用四个连续的空格来表示缩进。

行间空格后的缩进

当你使用行延续来保持行在 79 个字符以下时,使用缩进来提高可读性是有用的。它允许读者区分两行代码和跨行的两行代码。有两种缩进风格可供使用:

  1. 与开头分隔符对齐

  2. 悬挂式缩进

其中第一种方法是让缩进的代码块与开始的分隔符对齐:

def function(arg_one, arg_two,
             arg_three, arg_four):
    return arg_one

有时你会发现四个空格与开头分隔符完全对齐,如果你对PEP 8如何解决这些边缘情况感到好奇,那么你可以展开下面的可折叠部分:

产生视觉冲突的行延续经常出现在跨行 if 语句中,因为 if 、空格和左括号组成了四个字符,在这种情况下,很难确定 if 语句中的嵌套代码块从哪里开始:

x = 5
if (x > 3 and
    x < 10):
    print(x)

PEP 8 没有对如何解决这种情况采取严格的立场,但它提供了两种可选方案来帮助提高可读性。

一种可能的解决方法是在 final 条件之后添加注释,由于大多数编辑器都有语法高亮功能,这样可以将条件从嵌套的代码中分离出来:

x = 5
if (x > 3 and
    x < 10):
    # Both conditions satisfied
    print(x)

注释在视觉上将多行 if 语句与缩进的代码块分开。

PEP 8 提到的另一个选项是在行继续符上添加额外的缩进:

x = 5
if (x > 3 and
        x < 10):
    print(x)

通过在行继续符上添加额外的缩进,可以快速直观地知道哪些缩进代码仍然属于  if  语句,哪些代码属于缩进块。

换行符后面的另一种缩进样式是悬挂缩进。这是一个排版术语,意思是段落或语句中除第一行外的每一行都缩进。你可以使用悬挂缩进来直观地表示代码行的延续:

var = function(
    arg_one, arg_two,
    arg_three, arg_four)

你缩进第一个参数 arg_one ,使用悬挂缩进。进一步的行延续应该遵循与第一个缩进行相同的缩进级别。

注意,如果你使用悬挂缩进,第一行上不能有任何参数。下面的例子不符合PEP 8:

❌ Not recommended

var = function(arg_one, arg_two,
    arg_three, arg_four)

这个例子不符合PEP8,因为你把 arg_one 和 arg_two 放在了第一行开括号的旁边。

当你使用悬挂缩进时,你也应该添加额外的缩进,以区分函数内部的代码和连续行。下面的例子很难阅读,因为函数内部的代码和连续行处于相同的缩进级别:

​❌ Not recommended

def function(
    arg_one, arg_two,
    arg_three, arg_four):
    return arg_one

相反,最好在行继续处使用双重缩进,这有助于区分函数参数和函数体,提高可读性:

✅ Recommended

def function(
        arg_one, arg_two,
        arg_three, arg_four):
    return arg_one

当你按照Python代码风格指南来编写代码时,79个字符的行限制会迫使你在代码中添加换行符,为了提高可读性,你应该缩进连续的行以表明这是连续的行。

如上所示,有两种方法可以做到这一点:

  1. 将缩进块与开始分隔符对齐。

  2. 使用悬挂缩进。

你可以自由选择这两种方法中的任何一种来对换行后的代码进行缩进。

关闭括号的位置

行延续允许你在括号、方括号或大括号内断行。编程时,闭括号可能不是你主要的关注点,但把它放在合适的地方仍然很重要。否则,它会使读者感到困惑。

PEP 8 为隐含行继续符中的右括号位置提供了两种选择:

  • 将右括号与前一行的第一个非空格字符对齐:Python

list_of_numbers = [
    1, 2, 3,
    4, 5, 6,
    7, 8, 9
    ]

将结束括号与开始该结构的行中的第一个字符对齐:

list_of_numbers = [
    1, 2, 3,
    4, 5, 6,
    7, 8, 9
]

你可以自由选择任何一种方法。但是,一如既往,一致性是关键,所以尽量坚持上述方法之一。

注释

“If the implementation is hard to explain, it’s a bad idea.”

— The Zen of Python

你应该在编写代码时使用注释来记录代码。记录代码很重要,这样你和任何合作者都能理解它。当你或其他人阅读注释时,他们应该能够轻松理解注释所应用的代码,并知道它如何与代码的其余部分相适应。

在代码中添加注释时,需要记住以下几点:

  • 将注释和文档字符串的行长限制为 72 个字符。

  • 使用完整的句子,以大写字母开头。

  • 如果你更改了代码,请确保更新注释。

记住这些要点,是时候研究一下 PEP 8 中关于评论的建议的实质了。

块注释

使用代码块注释来记录一小部分代码。当你必须编写几行代码来执行一个操作时,例如从文件中导入数据或更新数据库条目,这些注释很有用。它们在帮助其他人理解给定代码块的目的和功能方面很重要。

PEP 8 为编写块注释提供了以下规则:

  • 将块注释缩进到与其所描述的代码相同的级别。

  • 以  #  开头,后面跟着一个空格。

  • 用一行包含单个  #  的行分隔段落。

下面是一个块注释,解释了  for  循环的功能。注意,为了保持 79 个字符的行限制,句子被换行:

for number in range(0, 10):
    # Loop over `number` ten times and print out the value of `number`
    # followed by a newline character.
    print(i, "\n")

有时候,如果代码非常技术化,那么有必要在块注释中使用多于一段的注释:

Python

# Calculate the solution to a quadratic equation using the quadratic
# formula.
#
# A quadratic equation has the following form:
# ax**2 + bx + c = 0
#
# There are always two solutions to a quadratic equation, x_1 and x_2.
x_1 = (-b + (b**2 - 4 * a * c) ** (1 / 2)) / (2 * a)
x_2 = (-b - (b**2 - 4 * a * c) ** (1 / 2)) / (2 * a)

如果你对哪种注释类型合适有疑问,那么块注释通常是最好的选择,尽可能在代码中使用它们,但是如果对代码进行更改,请确保更新它们!

内联注释

内联注释解释了一段代码中的单个语句。它们可以提醒你,或者向别人解释为什么某一行代码是必要的。下面是PEP 8对它们的说明:

  • 少用内联注释。

  • 在内联注释所引用的语句的同一行上写注释。

  • 用两个或多个空格将内联注释与语句分开。

  • 开始内联注释时,使用  #  和一个空格,就像块注释一样。

  • 不要用它们来解释显而易见的事情。

下面是一个内联注释的例子:

x = 5  # This is an inline comment

有时,内联注释似乎是必要的,但你可以使用更好的命名约定来代替,如下例:

❌ Not recommended

x = "John Smith"  # Student Name

这里,内联注释确实提供了额外的信息。然而,使用  x  作为人名的变量名是一种不好的实践。如果你重命名你的变量,就不需要内联注释了:

✅ Recommended

student_name = "John Smith"

最后,像下面这样的内联注释是糟糕的做法,因为它们陈述的是显而易见的,并且会使代码变得混乱:


❌ Not recommended

empty_list = []  # Initialize empty list

x = 5
x = x * 5  # Multiply x by 5

这两个注释都没有添加代码已经清楚显示的信息,最好避免编写这样的注释。

注意:你应该尽可能地让代码不言自明,保留注释以备需要额外解释的情况。例如,你可能需要注释来解释为什么你以某种方式编写代码。

内联注释比块注释更具体,而且很容易在不必要的时候添加它们,这会导致代码混乱。你可以只使用块注释。除非你确定你需要内联注释,否则如果你坚持只使用块注释,你的代码更有可能遵循 Python 代码的风格指南。

文档字符串

文档字符串(docstring)是出现在任何函数、类、方法或模块的第一行中,由三对双引号( """ )或三对单引号( ''' )括起来的字符串:

Pythondocumented_module.py

1"""This is a docstring."""
 2
 3# ...

你使用文档字符串来解释和记录特定的代码块,它们是 Python 的重要组成部分,你可以使用对象的  .__doc__  属性或  help()  函数来访问对象的文档字符串:

>>> import documented_module

>>> documented_module.__doc__
'This is a docstring.'

>>> help(documented_module)
Help on module documented_module:

NAME
    documented_module - This is a docstring.

FILE
    ./documented_module.py

虽然 PEP 8 提到了文档字符串,但它们代表了一个足够大的主题,因此有一个单独的文档,即 PEP 257,完全专门用于文档字符串。

主要的收获是,docstrings 是一种结构化的方法来记录 Python 代码,你应该为所有的公共模块、函数、类和方法编写它们。

如果实现很简单,那么你可以使用单行文档字符串,这样你可以将整个文档字符串放在同一行:

def adder(a, b):
    """Add a to b."""
    return a + b

如果实现更复杂,那么你将需要更多的行来创建一个有用的文档字符串,在这种情况下,你应该在第一行中开始概述描述,并以句号结束该行。

然后你可以使用更多的文本来记录代码对象。在文档字符串的这一部分,你还可以包括参数和返回值的描述。

最后,你应该把结束多行文档字符串的三个引号单独放在一行上:

def quadratic(a, b, c):
    """Solve quadratic equation via the quadratic formula.

    A quadratic equation has the following form:
    ax**2 + bx + c = 0

    There always two solutions to a quadratic equation: x_1 & x_2.
    """
    x_1 = (-b + (b**2 - 4 * a * c) ** (1 / 2)) / (2 * a)
    x_2 = (-b - (b**2 - 4 * a * c) ** (1 / 2)) / (2 * a)

    return x_1, x_2

表达式和语句中的空格

“Sparse is better than dense.”

— The Zen of Python

当你正确使用空格时,它在表达式和语句中非常有用。如果没有足够的空格,那么代码可能很难阅读,因为它们都堆在一起了。然而,如果有太多的空格,那么在语句中将相关术语组合在一起就很难视觉化了。

二进制运算符周围的空格

为了根据PEP 8达到最佳的可读性,在以下二进制操作符的两边都用一个空格:

  • 赋值运算符:= , += , -= ,等等

  • 比较:== , ,  !=  > , .  <  >=  is  is not  in 、 、  <= 和 not in

  • 布尔值:and , not ,和 or

当你使用等号( = )为参数赋值时,不要用空格包围它:

# ✅ Recommended
def function(default_parameter=5):
    # ...


# ❌ Not recommended
def function(default_parameter = 5):
    # ...

避免使用空格来表示参数的默认值,这可以使函数和方法的定义更加简洁。

当语句中包含多个运算符时,如果在每个运算符前后都添加一个空格,看起来会很混乱。相反,最好只在优先级最低的运算符周围添加空格,特别是在执行数学操作时。

何时避免添加空格

在某些情况下,添加空格会使代码更难阅读。太多的空格会使代码过于稀疏,难以理解。PEP 8 列出了不适当使用空格的清晰示例。

避免添加空格的最重要的地方是在一行的末尾,这被称为尾随空格,它是不可见的,在版本控制中会产生噪声差异,甚至在某些情况下会产生错误:

Pythontrailing_whitespace.py

x = 1 + 2 + \ 
    3 + 4

在上面的示例文件中,你尝试使用行延续标记在两行上继续赋值表达式。然而,你在反斜杠( \ )之后和换行符之前留下了尾随空格。

尾部的空格会阻止 Python 将其理解为行继续标记,并导致语法错误:

$ python trailing_whitespace.py
  File "trailing_whitespace.py", line 1
    x = 1 + 2 + \ 
                 ^
SyntaxError: unexpected character after line continuation character

虽然 Python 会注意到这个问题并通知你,但最佳实践是在 Python 代码中避免任何尾随空格。

PEP 8 还列出了应该避免空格的其他一些情况:

  • 紧接在括号、方括号或大括号内:Python

# ✅ Recommended
numbers = [1, 2, 3]

# ❌ Not recommended
numbers = [ 1, 2, 3, ]
  • 在逗号、分号或冒号前:Python

x = 5
y = 6

# ✅ Recommended
print(x, y)

# ❌ Not recommended
print(x , y)
  • 在函数调用的参数列表开始的圆括号之前:Python

def double(x):
    return x * 2

# ✅ Recommended
double(3)

# ❌ Not recommended
double (3)
  • 在开始索引或片段的左括号之前:Python

# ✅ Recommended
a_list[3]

# ❌ Not recommended
a_list [3]
  • 在尾部逗号和闭括号之间:Python

# ✅ Recommended
a_tuple = (1,)

# ❌ Not recommended
a_tuple = (1, )
  • 对齐赋值运算符:Python

# ✅ Recommended
var1 = 5
var2 = 6
some_long_var = 7

# ❌ Not recommended
var1          = 5
var2          = 6
some_long_var = 7

最重要的是确保代码中没有尾随空格,还有其他PEP 8不鼓励添加额外空格的情况,例如在括号内,以及在逗号和冒号之前,也不应该为了对齐操作符而添加额外的空格。

编程建议

“Simple is better than complex.”

— The Zen of Python

你经常会发现在Python中,像在其他编程语言中一样,有多种方法来执行类似的操作,在这一节中,你将看到PEP 8提供的一些建议,这些建议可以消除这种歧义并保持一致性。

不要使用等价运算符将布尔值与  True  或  False  进行比较。你经常需要检查一个布尔值是 true 还是 false。你可能希望使用如下的语句来完成此操作:

❌ Not recommended

is_bigger = 6 > 5
if is_bigger == True:
    return "6 is bigger than 5"

这里没有必要使用等价运算符( == )。bool  只能取值  True  或  False 。写下以下代码就足够了:

✅ Recommended

is_bigger = 6 > 5
if is_bigger:
    return "6 is bigger than 5"

这种用布尔值来执行  if  语句的方式需要更少的代码,而且更简单,所以 PEP 8 鼓励使用它。

在 if 语句中使用空序列为假的事实。如果你想检查一个列表是否为空,你可能会试图检查列表的长度。如果列表为空,那么它的长度是 0 ,当你在 if 语句中使用它时,它等于 False 。下面是一个例子:

❌ Not recommended

a_list = []
if not len(a_list):
    print("List is empty!")

然而,在 Python 中,任何空列表、字符串或元组都是假的,因此你可以用更简单的方法来代替上面的方法:

✅ Recommended

a_list = []
if not a_list:
    print("List is empty!")

虽然这两个例子都会打印出  List is empty! ,但第二个选项更容易阅读和理解,因此 PEP 8 鼓励使用它。

在 if 语句中使用 is not 而不是 not ... is 。如果你试图检查一个变量是否具有定义的值,有两种选择。第一种是用 x is not None 来计算 if 语句,如下面的例子:

✅ Recommended

if x is not None:
    return "x exists!"

第二种选择是计算  x is None ,然后根据  not  结果生成一个  if  语句:

❌ Not recommended

if not x is None:
    return "x exists!"

虽然 Python 会正确地计算这两个选项,但第一个选项更直接,因此 PEP 8 鼓励使用它。

当你指的是 if x is not None: 时,不要使用 if x: 。有时,你可能有一个函数,其默认参数是 None 。当检查这样的参数, arg ,是否具有不同的值时,一个常见的错误是使用以下代码:

❌ Not recommended

if arg:
    print(arg)

这段代码检查  arg  是否为真。相反,你想检查  arg  是否为  not None ,所以最好使用下面的代码:

✅ Recommended

if arg is not None:
    print(arg)

这里的错误是假设 not None 和truthy是等价的,但它们不是.你可以设置 arg 为一个空列表( [] ).正如你上面看到的,在Python中,空列表也被评估为falsy.所以,即使你给 arg 赋值,条件也不满足,Python也不会执行 if 语句中的代码:

>>> arg = []

>>> if arg:
...     print(arg)
...

>>> if arg is not None:
...     print(arg)
...
[]

你可以看到,当你在Python中处理假值时,这两种方法会产生不同的结果。

使用  .startswith()  和  .endswith()  而不是切片。如果你试图检查字符串  word  是否在前缀或后缀中使用了单词 cat,那么使用列表切片似乎是合理的。然而,列表切片容易出错,而且你必须硬编码前缀或后缀中的字符数。对于不熟悉 Python 列表切片的人来说,他们也不清楚你试图实现什么:

❌ Not recommended

if word[:3] == "cat":
    print("The word starts with 'cat'")

然而,这不像使用  .startswith()  那样可读:

✅ Recommended

if word.startswith("cat"):
    print("The word starts with 'cat'")

同样,当你检查后缀时,同样的原则也适用。下面的例子概述了如何检查字符串是否以  "jpg"  结尾:

❌ Not recommended

if file_name[-4:] == ".jpg":
    print("The file is a JPEG")

虽然结果是正确的,但这种符号有点笨重,难以阅读,你可以使用  .endswith() ,如下面的例子所示:

✅ Recommended

if file_name.endswith(".jpg"):
    print("The file is a JPEG")

和大多数编程建议一样,我们的目标是可读性和简洁性,在 Python 中,有很多不同的方法来执行相同的操作,因此选择方法的指导方针可能是有帮助的。

何时忽略PEP 8

这个问题的简短回答是永远不会。如果你严格遵循PEP 8,你可以保证你将拥有干净、专业和可读的代码。这将有利于你以及合作者和潜在的雇主。

然而,在以下情况下,PEP 8中的一些指南是不方便的:

  • 如果遵循PEP 8会破坏与现有软件的兼容性

  • 如果你正在处理的代码与PEP 8不一致

  • 如果代码需要与旧版本的 Python 兼容,则可以使用以下代码:

PEP 8 用一小段文字说明,如果有充分的理由不应用 PEP 8,那么就不应该应用 PEP 8,它还提到,最重要的是保持代码在编写它的上下文中的一致性。

帮助确保你的代码遵循PEP 8的提示和技巧

要确保你的代码遵循 Python 代码的风格指南,需要记住很多东西。在开发代码时,记住所有这些规则可能是一个很高的要求。更新过去的项目以符合 PEP 8 尤其耗时。幸运的是,有一些工具可以帮助加速这个过程。有两类工具可以帮助你强制执行这些风格规则:linter 和 autoformatters。

Linters

Linters 是分析代码并标记错误的程序。它们提供如何修复每个错误的建议。当作为文本编辑器的扩展安装时,Linters 特别有用,因为它们在编写代码时标记错误和风格问题。在本节中,你将看到一些流行的 Linter 如何工作的大纲,并在最后提供文本编辑器扩展的链接。

一些好的Python代码检查器是 pycodestyle , flake8 ,和 ruff .你可以用下面的代码片段来试试,它包含了不符合PEP 8的格式:

import math

numbers = [1,2,\ 
3,4]

def add_all_numbers_from_collection(
    number_one, number_two, number_three,
    number_four):
    return number_one+number_two + number_three +number_four

print (add_all_numbers_from_collection( *numbers ))

pycodestyle  是一个工具,可以根据 PEP 8 中的一些样式约定检查 Python 代码。

然后,你可以在终端中运行  pycodestyle ,将你想要检查的 Python 文件作为参数传递给它:

$ pycodestyle unfashionable.py
unfashionable.py:3:13: E231 missing whitespace after ','
unfashionable.py:3:15: E231 missing whitespace after ','
unfashionable.py:3:16: E502 the backslash is redundant between brackets
unfashionable.py:4:1: E128 continuation line under-indented for visual indent
unfashionable.py:4:2: E231 missing whitespace after ','
unfashionable.py:6:1: E302 expected 2 blank lines, found 1
unfashionable.py:8:5: E125 continuation line with same indent as next logical line
unfashionable.py:9:50: E225 missing whitespace around operator
unfashionable.py:11:1: E305 expected 2 blank lines after class or function definition, found 1
unfashionable.py:11:6: E211 whitespace before '('
unfashionable.py:11:40: E201 whitespace after '('
unfashionable.py:11:49: E202 whitespace before ')'
unfashionable.py:11:52: W292 no newline at end of file

linter 会输出遇到样式冲突的行号和列号,它还会给出特定样式冲突的错误代码,以及问题的简短描述。

另一个流行的Python代码检查选项是 flake8 ,它是一个结合了其他项目的工具,例如使用 pyflakes 和 pycodestyle 进行错误检测。

同样,你可以使用  pip  安装  flake8 :

$ python -m pip install flake8

然后,你可以在终端中运行  flake8 ,并把你想要检查的文件作为参数传递给它:

$ flake8 unfashionable.py
unfashionable.py:1:18: E999 SyntaxError: unexpected character after line continuation character

哦!看起来你的代码除了格式混乱之外还包含一个语法错误!虽然 pycodestyle 没有标记它,但 flake8 识别了错误并向你展示如何修复它。

在你通过删除第3行末尾的空格修复了语法错误后,再次运行 flake8 ,会显示与之前几乎相同的风格冲突:

$ flake8 unfashionable.py
unfashionable.py:1:1: F401 'math' imported but unused
unfashionable.py:3:13: E231 missing whitespace after ','
unfashionable.py:3:15: E231 missing whitespace after ','
unfashionable.py:3:16: E502 the backslash is redundant between brackets
unfashionable.py:4:1: E128 continuation line under-indented for visual indent
unfashionable.py:4:2: E231 missing whitespace after ','
unfashionable.py:6:1: E302 expected 2 blank lines, found 1
unfashionable.py:8:5: E125 continuation line with same indent as next logical line
unfashionable.py:9:50: E225 missing whitespace around operator
unfashionable.py:11:1: E305 expected 2 blank lines after class or function definition, found 1
unfashionable.py:11:6: E211 whitespace before '('
unfashionable.py:11:40: E201 whitespace after '('
unfashionable.py:11:49: E202 whitespace before ')'
unfashionable.py:11:52: W292 no newline at end of file

除了标记为  E  错误的 PEP 8 风格违背外,你还得到了一个  F  错误类型,这个错误告诉你在你的脚本中有一个未使用的  math  模块的导入。

pycodestyle  纯粹检查 PEP 8 风格的违反,而  flake8  结合了多种工具,因此也可以帮助你识别语法错误、逻辑错误(如未使用的导入),甚至复杂性问题。你很快就会了解  ruff 。

许多linter也可以作为Sublime Text、Visual Studio Code甚至VIM的扩展。如果你还没有找到你最喜欢的IDE或文本编辑器,那么你可以从学习VS Code或Sublime Text开始。

好了,linter 给了你很多关于在  unfashionable.py  中修复什么的好提示。但是,似乎要应用所有这些建议需要很多工作。如果计算机可以为你做这项工作,那就太好了。这正是你可以使用自动格式化器的原因。

Autoformatters

自动格式化程序是自动重构代码以符合PEP 8的程序。一旦这样的程序是 black ,它会自动格式化代码,遵循PEP 8中的大部分规则。一个很大的区别是它将行长限制为88个字符,而不是79个字符。然而,你可以通过添加一个命令行标志来覆盖它,正如下面的例子所示。

注意:另外两个自动格式化器, autopep8  和  yapf ,执行的操作与  black  类似。Ruff 也支持自动格式化。

你可以使用  pip  安装  black :

$ python -m pip install black

你可以在命令行中运行  black ,就像你之前使用 linters 一样。再看一下  unfashionable.py  中你想要修复的代码:

import math

numbers = [1,2,\
3,4]

def add_all_numbers_from_collection(
    number_one, number_two, number_three,
    number_four):
    return number_one+number_two + number_three +number_four

print (add_all_numbers_from_collection( *numbers ))

注意,这个版本的代码不包括前面修复的尾随空格,如果代码有语法错误,那么  black  会告诉你,并且在你修复错误之前不会对代码进行格式化。

然后,你可以通过命令行运行以下命令:

$ black unfashionable.py
reformatted unfashionable.py

All done! ✨ 🍰 ✨
1 file reformatted.

在 black 自动重格式化 unfashionable.py 之后,它看起来像这样:

import math

numbers = [1, 2, 3, 4]


def add_all_numbers_from_collection(number_one, number_two, number_three, number_four):
    return number_one + number_two + number_three + number_four


print(add_all_numbers_from_collection(*numbers))

这看起来比之前好很多,但是  add_all_numbers_from_collection()  的所有参数使函数定义超出了 PEP 8 建议的 79 个字符的限制,正如你之前所学到的, black  使用了 88 个字符作为限制。

如果你想改变行长限制,那么你可以使用 --line-length 标志:

$ black --line-length=79 unfashionable.py
reformatted unfashionable.py

All done! ✨ 🍰 ✨
1 file reformatted.

在第二次运行时限制了行长之后,自动格式化的代码现在看起来很棒,并且很好地遵循了 Python 代码的风格指南:

import math

numbers = [1, 2, 3, 4]


def add_all_numbers_from_collection(
    number_one, number_two, number_three, number_four
):
    return number_one + number_two + number_three + number_four


print(add_all_numbers_from_collection(*numbers))

然而,你可能已经注意到在你的文件顶部仍然有未使用的导入 math 。当你使用 flake8 检查你的文件时,你仍然可以识别问题:

$ flake8 unfashionable.py
unfashionable.py:1:1: F401 'math' imported but unused

如果有一个工具,结合检查和格式在一个屋檐下不是很好吗?

组合工具

Ruff 是 Python 社区中一个流行的工具,它既可以作为 linter 也可以作为自动格式化器,它是用 Rust 编写的命令行工具,因此执行起来非常快。

你可以使用  pip  安装 Ruff:

$ python -m pip install ruff

现在你可以同时使用 Ruff 来检查和格式化代码:

$ ruff check unfashionable.py
unfashionable.py:1:8: F401 [*] `math` imported but unused
Found 1 error.
[*] 1 fixable with the `--fix` option.

Ruff 会立即找到未使用的导入,甚至会给你一个选项来修复它。按照输出中的建议,自动修复未使用的  math  导入:

$ ruff check --fix unfashionable.py
Found 1 error (1 fixed, 0 remaining).

如果你在初始的未格式化的 unfashionable.py 文件上运行检查,那么你会注意到Ruff不会标记 pycodestyle 或 flake8 所标记的样式冲突。

而且因为Ruff既是一个linter也是一个自动格式化器,你甚至不需要担心它,如果你运行这个工具的 format 命令,那么它会修复所有PEP 8风格的违规,而你甚至不需要知道它们在第一位:

$ ruff format unfashionable.py
1 file reformatted

使用默认设置,Ruff自动格式化器将生成以下格式化代码:

numbers = [1, 2, 3, 4]


def add_all_numbers_from_collection(number_one, number_two, number_three, number_four):
    return number_one + number_two + number_three + number_four


print(add_all_numbers_from_collection(*numbers))

注意,行号再次比 PEP 8 推荐的更晚断开,但是你可以在 Ruff 的设置中更改这个设置以及许多其他设置。

如果你想了解更多关于使用lint和autoformatters的知识,你可以看看Python Code Quality: Tools & Best Practices,它对如何使用这些工具进行了全面的解释。

现在你知道如何使用PEP 8中的指导方针来编写高质量、可读性强的Python代码了,虽然这些指导方针看起来有些迂腐,但遵循它们确实可以提高你的代码质量,特别是在你需要与潜在的雇主或合作者分享代码时。

- END -


下方扫码关注 软件质量保障,与质量君一起学习成长、共同进步,做一个职场最贵Tester!

往期推荐

聊聊工作中的自我管理和向上管理

经验分享|测试工程师转型测试开发历程

聊聊UI自动化的PageObject设计模式

细读《阿里测试之道》

  • 26
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

软件质量保障

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值