教你一招,提升你Python代码的可读性,小技巧
Python的初学者,开发者都应该知道的代码可读性提高技巧,本篇主要介绍了如下内容:
- PEP 8是什么以及它存在的原因
- 为什么你应该编写符合PEP 8标准的代码
- 如何编写符合PEP 8的代码
为什么我们需要PEP 8?
PEP 8 的存在是为了提高Python代码的可读性的。但为什么可读性如此重要?为什么编写可读代码是Python语言的指导原则之一?
正如Guido van Rossum所说:“代码的阅读频率远高于编写代码的频率”。比如,你可能花费几分钟或一整天的时间编写一段代码。一旦你写完它,你可能就再也不会写了,但你肯定要再读一遍。或者,这段代码可能仍然是你正在进行的项目的一部分。每次回到该文件时,你都必须记住该代码的作用以及编写代码的原因,因此可读性非常重要。
如果你是Python的新手,可能很难记住几天或几周之前编写代码的作用。但如果你遵循PEP 8,你就很好地命名了你的变量,添加了足够的空格更容易遵循代码中的逻辑步骤,还可以很好地注释你的代码。所有这些都意味着你的代码更易读。
如果你正在寻找开发工作,那么遵循PEP 8尤为重要。编写清晰易读的代码显示出专业性。它会告诉你的老板你知道如何很好地构建代码。
如果你有更多编写Python代码的经验,那么你可能需要与其他人协作。其他人可能从未见过你或以前见过你的编码风格,必须阅读并理解你的代码。
命名的规定
编写Python代码时,必须列举很多东西:变量,函数,类,包等。选择合理的名字将为你节省很多时间和精力。你将能够从名称中读懂某个变量,函数或类所代表的含义。你还可以避免使用那些可能导致后期难以调试错误的不适当的名称。
命名样式
下表总结了Python代码中的一些常见类型和命名规则:
1.png为了编写可读代码,你仍然需要谨慎选择字母和单词。除了在代码中选择正确的命名样式外,还必须仔细选择名称。
如何选择名称
在编写代码时,你应该在命名选择中加入相当多的思考,因为这样可以使代码更具可读性。在Python中命名对象的最佳方法是使用描述性名称来清楚表明对象所代表的内容。
命名变量时,你可能会选择简单的单字母小写名称,例如x。但是,除非你使用x作为数学函数的参数,否则不清楚x代表什么。想象一下,你将一个人的姓名存储为字符串,并且你希望使用字符串切片来格式化其名称。你最终会得到这样的东西:
>>> # Not Recommended
>>> x = 'John Smith'
>>> y, z = x.split()
>>> print(z, y, sep=', ')
'Smith, John'
这是有效的,但你必须跟踪x,y和z代表的内容。这可能让其他人产生困惑,更明确的名称选择将是这样的:
>>> # Recommended
>>> name = 'John Smith'
>>> first_name, last_name = name.split()
>>> print(last_name, first_name, sep=', ')
'Smith, John'
同样,为了减少你所做的输入量,在选择名称时使用缩写是很有帮助的。在下面的例子中,我们定义了一个函数db(),它接受一个参数x并将其加倍:
# Not Recommended
def db(x):
return x * 2
乍一看,这似乎是一个明智的选择。 db()很容易成为double的缩写。但想象一下,几天后回到这段代码,你可能已经忘记了你试图通过这个功能实现的目标,这会花很长时间回想。
以下示例更加清晰。如果你在编写代码后几天回到此代码,你仍然可以阅读并理解此函数的用途:
# Recommended
def multiply_by_two(x):
return x * 2
同样的理念适用于Python中的所有其他数据类型和对象,要始终尽量使用最简洁但最具描述性的名称。
代码布局
如何布置代码对于它的可读性有很大的作用。此处,你将学习如何添加垂直空格以提高代码的可读性,以及如何处理PEP 8中建议的79字符行限制。
空白行
垂直空白或空行可以极大地提高代码的可读性。聚集在一起的代码可能是压倒性的,难以阅读。同样,代码中的空行太多会使其看起来非常稀疏,读者可能需要滚动超过必要的行。以下是关于如何使用垂直空白的三个关键指南。
1.最外层函数和类之间要留有两个空行。最高层函数和类应该是自包含的,并处理单独的功能。在它们周围放置额外的垂直空间是有意义的,因此很明显它们是分开的:
class MyFirstClass:
pass
class MySecondClass:
pass
def top_level_function():
return None
2.一个类里面的方法之间用一个空行。在一个类中,方法都彼此相关。最好只在它们之间留一行:
class MyClass:
def first_method(self):
return None
def second_method(self):
return None
3.在函数内使用空白行以显示清晰的步骤。有时,复杂的函数必须在return语句之前完成几个步骤。为了帮助读者理解函数内部的逻辑,在每个步骤之间留一个空行会很有帮助。
在下面的示例中,有一个计算列表方差的函数。这是两步问题,所以我在每个步骤之间留下了一个空行。在return语句之前还有一个空行。这有助于读者清楚地看到返回的内容:
def calculate_variance(number_list):
sum_list = 0
for number in number_list:
sum_list = sum_list + number
mean = sum_list / len(number_list)
sum_squares = 0
for number in number_list:
sum_squares = sum_squares + number**2
mean_squares = sum_squares / len(number_list)
return mean_squares - mean**2
如果仔细使用垂直空白,可以大大提高代码的可读性。它有助于读者直观地理解你的代码如何分成几个部分,以及这些部分如何相互关联。
最大行长度和换行
PEP 8建议行数限制为79个字符。当然,将语句保持在79个字符以内并不总是可行的。 PEP 8给出了允许语句在多行上运行的方法。
如果代码包含在括号,方括号或大括号中,Python将假定行继续:
def function(arg_one, arg_two,
arg_three, arg_four):
return arg_one
如果不能使用按规则继续,那么你可以使用反斜杠来代替换行:
from mypkg import example1,
example2, example3
如果需要在二元运算符周围换行,例如 + 和 ,则它应该在运算符之前发生。数学家认为在二元运算符之前换行可提高可读性*。比较以下两个例子。
下面是在二元运算符之前换行的示例:
# Recommended
total = (first_variable
+ second_variable
- third_variable)
你可以立刻看到正在添加或减去的变量,因为操作符紧挨着正在操作的变量。 现在,让我们看一个在二元运算符之后换行的示例:
# Not Recommended
total = (first_variable +
second_variable -
third_variable)
在这里,很难看出哪个变量被添加,哪个变量被减去。
缩进
缩进或前导空格在Python中非常重要。Python中代码行的缩进级别决定了语句如何组合在一起。 请看以下示例:
x = 3
if x > 5:
print('x is larger than 5')
缩进的print语句让Python知道只有if语句返回True才能执行它。相同的缩进会告诉Python在调用函数或代码属于给定类时要执行的代码。 PEP 8给出的关键缩进规则如下:
- 使用4个连续的空格来表示缩进;
- 首选使用空格,再使用Tab;
Tab键与空格
如上所述,在缩进代码时应首先使用空格而不是Tab键。你可以在文本编辑器中调整Tab设置:当你按Tab键时,输出4个空格而不是Tab符号。
如果你使用的是Python 2,并且混合使用了Tab和空格来缩进代码,那么在运行它时将不会看到错误。为了帮助你检查一致性,可以在从命令行运行Python 2代码时添加 -t 标志。当你使用的Tab和空格不一致时,解释程序将发出警告:
$ python2 -t code.py
code.py: inconsistent use of tabs and spaces in indentation
如果使用 -tt 标志,则解释器将发出错误而不是警告,并且你的代码将不会运行。使用此方法的好处是解释器会告诉你不一致的位置:
$ python2 -tt code.py
File "code.py", line 3
print(i, j)
^
TabError: inconsistent use of tabs and spaces in indentation
Python 3不允许混合Tab和空格。因此,如果你使用的是Python 3,则会自动发出以下错误:
$ python3 code.py
File "code.py", line 3
print(i, j)
^
TabError: inconsistent use of tabs and spaces in indentation
你可以使用Tab或空格来编写Python代码,以表示缩进。但是,如果你使用的是Python 3,则必须与你的选择保持一致。否则,代码将无法运行。PEP 8建议始终使用4个连续空格来表示缩进。
换行后的缩进
当使用换行将行保持在79个字符以下时,使用缩进来提高可读性是很有用的。它允许读者区分两行代码和跨越两行的单行代码。你可以使用两种缩进样式。
第一个是将缩进块与开口分隔符对齐:
def function(arg_one, arg_two,
arg_three, arg_four):
return arg_one
有时你会发现只需要4个空格就可以与开口分隔符对齐。这通常发生在跨越多行的if语句中,因为if,space和opening括号恰巧组成4个字符。在这种情况下,可能很难确定if语句中嵌套代码块的开始位置:
>x = 5
if (x > 3 and
x < 10):
print(x)
在这种情况下,PEP 8提供了两种替代方案来帮助提高可读性:
1. 在条件之后添加注释
>x = 5
if (x > 3 and
x < 10):
# Both conditions satisfied
print(x)
2. 在换行中添加额外的缩进
>x = 5
if (x > 3 and
x < 10):
print(x)
第二个换行符后缩进方式是悬挂缩进:
这意味着除了段落或语句中的第一行之外的每一行都是缩进的。你可以使用悬挂缩进来直观地表示代码行延续。下面一个例子:
var = function(
arg_one, arg_two,
arg_three, arg_four)
使用悬挂缩进时,添加额外的缩进以区分连续行与函数内包含的代码。以下示例很难阅读,因为函数内部的代码与连续行的缩进级别相同:
# Not Recommended
def function(
arg_one, arg_two,
arg_three, arg_four):
return arg_one
因此,最好在下一行使用双缩进。这有助于区分函数参数和函数体,从而提高可读性:
def function(
arg_one, arg_two,
arg_three, arg_four):
return arg_one
总结一下,换行后缩进有两种方法,第一种是将缩进块与开口分隔符对齐,第二种是使用悬挂缩进。你可以自由选择在换行符后使用哪种缩进方法。
在哪里放置右括号
换行允许你断开括号,方括号或大括号内的行。PEP 8为右中括号的位置提供了两个选项:
1. 使用前一行的第一个非空白字符排列右括号:
list_of_numbers = [
1, 2, 3,
4, 5, 6,
7, 8, 9
]
2. 将右括号与构造开始行的第一个字符对齐:
list_of_numbers = [
1, 2, 3,
4, 5, 6,
7, 8, 9
]
可以自由选择使用的选项,但一致性是关键,所以尽量坚持上述方法之一。
注释
注释代码非常重要,这样你和任何协作者都可以理解它。当你或其他人阅读评论时,他们应该能够轻松理解注释所对应的代码以及它与其余代码的匹配程度。
以下是在为代码添加注释时要记住的一些要点:
- 将注释和文档字符串的行长限制为72个字符;
- 使用完整的句子,以大写字母开头;
- 如果更改代码,请务必更新注释;
块注释
使用块注释来标注一小部分代码。当你必须编写多行代码来执行单个操作(例如从文件导入数据或更新数据库条目)时,它们非常有用。它们很重要,因为它们可以帮助其他人理解给定代码块的用途和功能。
PEP 8为编写块注释提供了以下规则:
- 将块注释缩进到与它们描述的代码相同的级别;
- #后面加单个空格后开始注释;
- 用包含单个#的行分隔段落;
这是一个解释for循环功能的块注释。请注意,句子换行到新行以保留79个字符行限制:
for i in range(0, 10):
# Loop over i ten times and print out the value of i, followed by a
# new line character
print(i, '
')
有时,如果代码是非常技术性的,那么有必要在块注释中使用多个段落:
def quadratic(a, b, c, x):
# Calculate the solution to a quadratic equation using the quadratic
# formula.
#
# 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)
return x_1, x_2
如果你对使用哪种注释类型更适合存在疑问,那么块注释通常是可行的方法。尽可能在整个代码中使用它们,但如果你对代码进行了更改,也请务必更新它们。
行注释
行注释解释了一段代码中的单个语句。它们有助于提醒你,或向其他人解释为什么需要某行代码。以下是PEP 8对它们的建议:
- 与代码写在同一行;
- 使用两个或多个空格将代码与行注释分开;
- #后加单个空格,然后进行行注释;
- 不要用它们来解释已经很明显的问题;
以下是行注释的示例:
x = 5 # This is an inline comment
有时,行注释似乎是必要的,但你可以使用更好的命名规则。比如,这是一个例子:
x = 'John Smith' # Student Name
这里,行注释确实提供了额外的信息。但是,使用x作为人名的变量名是不好的做法。如果重命名变量,则无需行注释:
student_name = 'John Smith'
最后,像这样的行注释是不好的做法,因为它们注释了明显且混乱的代码:
empty_list = [] # Initialize empty list
x = 5
x = x * 5 # Multiply x by 5
行注释比块注释更具体,但很容易在不需要时添加它们,这会导致混乱,而你可以总是使用块注释,而不必担心这些。因此,除非确定需要使用行注释,否则更多使用块注释,则代码更可能符合PEP 8。
文档字符串
文档字符串是用双(""")或单引号(''')括起来的字符串,它们出现在任何函数,类,方法或模块的第一行。你可以使用它们来解释和记录一个特定的代码块。
适用于文档字符串的最重要规则如下:
- 环绕文档字符串,两边都有三个双引号,如 """ 这是一个文档字符串 """;
- 为所有公共模块,函数,类和方法编写它们;
- 将单行结束多行文档字符串的 """ 放在一行上:
def quadratic(a, b, c, x):
"""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
- 对于单行文档字符串,请将 """ 保留在同一行:
def quadratic(a, b, c, x):
"""Use the quadratic formula"""
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
表达式和语句的空格
空格在表达式和语句中正确使用时非常有用。如果没有足够的空白,那么代码可能难以阅读,因为它们都聚集在一起。如果空白太多,那么在语句中可能难以在视觉上组合相关术语。
二元运算符两边的空白
以下二元运算符两边都有一个空格:
- 赋值运算符(=,+=, -=)
- 比较运算符(==,=!,>,<,>=,<=)和(is,is not,in,not in)
- 布尔运算法(and,not,or)
如果语句中有多个运算符,则在每个运算符之前和之后添加单个空格可能会让人感到困惑。相反,最好只在优先级最低的运算符两边添加空格,尤其是在执行数学运算时。以下是几个例子:
# Recommended
y = x**2 + 5
z = (x+y) * (x-y)
# Not Recommended
y = x ** 2 + 5
z = (x + y) * (x - y)
你还可以将此应用于有多个条件的if语句:
# Not recommended
if x > 5 and x % 2 == 0:
print('x is larger than 5 and divisible by 2!')
在上面的示例中,and 运算符具有最低优先级。因此,可以更清楚地表达if语句如下:
# Recommended
if x>5 and x%2==0:
print('x is larger than 5 and divisible by 2!')
你可以自由选择哪个更清晰,但需要注意的是必须在运算符的任何一侧使用相同数量的空格。以下是不可接受的:
# Definitely do not do this!
if x >5 and x% 2== 0:
print('x is larger than 5 and divisible by 2!')
在切片中,冒号充当二元运算符。因此,上面所说的规则同样适用,任何一方都应该有相同数量的空白。以下列表切片示例:
list[3:4]
# Treat the colon as the operator with lowest priority
list[x+1 : x+2]
# In an extended slice, both colons must be
# surrounded by the same amount of whitespace
list[3:4:5]
list[x+1 : x+2 : x+3]
# The space is omitted if a slice parameter is omitted
list[x+1 : x+2 :]
何时避免添加空格
在某些情况下,添加空格会使代码更难以阅读。太多的空格会使代码过于稀疏而难以理解。PEP 8总结了空白不合适的非常明显的例子。
避免添加空格的最重要的地方是在一行的末尾,这称为尾部空格。它是不容易察觉的,可能产生难以追踪的错误。
以下总结了一些应避免添加空格的情况:
- 紧靠括号,括号或大括号内:
# Recommended
my_list = [1, 2, 3]
# Not recommended
my_list = [ 1, 2, 3, ]
- 在逗号,分号或冒号之前:
x = 5
y = 6
# Recommended
print(x, y)
# Not recommended
print(x , y)
- 在打开函数调用的参数列表的开括号之前:
def double(x):
return x * 2
# Recommended
double(3)
# Not recommended
double (3)
- 在开始索引或切片的开括号之前:
# Recommended
list[3]
# Not recommended
list [3]
- 在末尾逗号和右括号之间:
# Recommended
tuple = (1,)
# Not recommended
tuple = (1, )
- 要对齐赋值运算符:
# Recommended
var1 = 5
var2 = 6
some_long_var = 7
# Not recommended
var1 = 5
var2 = 6
some_long_var = 7
确保代码中的任何位置都没有尾部空格。在其他情况下,PEP 8不鼓励添加额外的空格,例如立即在括号内,以及逗号和冒号之前。你也应该永远不要添加额外的空格为了对齐运算符。
编程建议
你经常会发现有几种方法可以在Python中执行相同的操作(以及任何其他编程语言)。下面你将看到PEP 8提供的一些建议,以消除这种歧义并保持一致性。
1. 不要使用等价运算符==将布尔值与True或False进行比较。你经常需要检查布尔值是True还是False。
# Not Recommended
my_bool = 6 > 5
if my_bool == True:
return '6 is bigger than 5'
这里不需要使用等价运算符==,bool只能取值True或False,因此写下面的内容就足够了,这种使用布尔值执行if语句的方法需要更少的代码并且更简单。
# Recommended
if my_bool:
return '6 is bigger than 5'
2. 在if语句中直接判断空序列是否为假。如果要检查列表是否为空,则可能需要检查列表的长度。如果列表为空,则其长度为0,在if语句中使用时等于False。这是一个例子:
# Not Recommended
my_list = []
if not len(my_list):
print('List is empty!')
但是,在Python中,任何空列表,字符串或元组都是假的。因此,我们可以给出一个更简单的替代方案:
# Recommended
my_list = []
if not my_list:
print('List is empty!')
虽然两个例子都打印出来了,第二个选项更简单,所以PEP 8鼓励使用它。
3. 在if语句中使用 is not 而非 not...is。如果你要检查变量是否具有已定义的值,则有两个选项。第一个是使用 x is not None,如下例所示:
# Recommended
if x is not None:
return 'x exists!'
第二种选择是 x is None,然后根据 not 结果得到if语句:
# Not Recommended
if not x is None:
return 'x exists!'
虽然两个选项都将被正确评估,但第一个选项更简单。
4. 当你的意思是x is not None 的时候,不要使用if x。有时,你可能拥有一个默认情况下参数为None的函数。检查这样的参数arg是否被赋予不同值时常见的错误是使用以下内容:
# Not Recommended
if arg:
# Do something with arg...
此代码检查arg是否为真。相反,你要检查arg是否为None,因此最好使用以下内容:
# Recommended
if arg is not None:
# Do something with arg...
这里犯的错误是假设了 not None和 truthy 是等价的。你可以设置arg = []。如上所述,空列表在Python中被评估为假的。因此,即使已经分配了参数arg,也不满足条件,因此不会执行if语句主体中的代码。
5. 使用.startswith()和.endswith()而不是切片。如果你尝试检查字符串单词是否带有前缀或带有后缀的单词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[-3:] == '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:linters 和autoformatters。
Linters
Linters是分析代码和标记错误的程序,它提供了有关如何修复错误的建议。当作为文本编辑器的扩展安装时,Linters特别有用,因为它们在你编写时标记错误和样式问题。下面你将看到Linkers的工作原理,然后和文本编辑器扩展的链接。
最好Linters的Python代码如下:
pycodestyle是一个根据PEP 8中的某些样式约定来检查Python代码的工具。使用pip安装pycodestyle:
$ pip install pycodestyle
可以使用以下命令终端运行pycodestyle:
$ pycodestyle code.py
code.py:1:17: E231 missing whitespace after ','
code.py:2:21: E231 missing whitespace after ','
code.py:6:19: E711 comparison to None should be 'if cond is None:'
flake8是一个结合了debugger,pyflakes和pycodestyle的工具。使用pip安装flake8:
$ pip install flake8
使用以下命令从终端运行flake8:
$ flake8 code.py
code.py:1:17: E231 missing whitespace after ','
code.py:2:21: E231 missing whitespace after ','
code.py:3:17: E999 SyntaxError: invalid syntax
code.py:6:19: E711 comparison to None should be 'if cond is None:':
Autoformatters
Autoformatters是重构ni1的代码以自动符合PEP 8的程序。一旦这样的程序是black,它按照PEP 8中的大多数规则自动编码代码。一个很大的区别是它将行长度限制为88个字符,而不是79。但是,你可以通过添加命令行标志来覆盖它,就像你一样我将在下面的例子中看到。
使用pip安装black。它需要Python 3.6+才能运行:
$ pip install black
它可以通过命令行运行,就像Linters一样。假设你从名为code.py的文件中开始使用以下不符合PEP 8的代码:
for i in range(0,3):
for j in range(0,3):
if (i==2):
print(i,j)
然后,您可以通过命令行运行以下命令:
$ black code.py
reformatted code.py
All done!
code.py将自动重新格式化为如下所示:
for i in range(0, 3):
for j in range(0, 3):
if i == 2:
print(i, j)
如果要更改行长度限制,则可以使用--line-length标志:
$ black --line-length=79 code.py
reformatted code.py
All done!
另外两个autoformatters,autopep8和yapf,执行与black相似的操作。
总结
本篇主要介绍了如下内容:
- PEP 8是什么以及它存在的原因;
- 为什么你应该编写符合PEP 8标准的代码;
- 如何编写符合PEP 8的代码;
除此之外,还介绍了如何使用linters和autoformatters根据PEP 8指南检查代码。
如果您想了解有关PEP 8的更多信息,那么你可以阅读完整的文档,或访问pep8.org,它包含相同的信息,但格式很好。
教你一招,提升你Python代码的可读性,小技巧