摘要
该列表主要是对Python哲学的幽默描述,但是多年来,许多Python应用程序已使用这些准则来极大地提高其代码的质量,可读性和可维护性。但是,仅列出Python的Zen并没有多大价值,
因此以下各节将更详细地解释每个习语。
美丽胜过丑陋
也许第一个概念可以说是整个主题中最主观的。毕竟,情人注视着情人,这个事实已经讨论了好几个世纪了。它公然提醒人们哲学不是绝对的。尽管如此,以书面形式提出这样的目标仍然是我们要追求的目标,这是所有这些理想的最终目的。这种哲学的一个明显的应用是Python自己的语言结构,
它最大程度地减少了标点符号的使用,而在适当的情况下更喜欢使用英语单词。
另一个优势是Python专注于 关键字参数,有助于弄清否则很难理解的函数调用。
考虑以下两种可能的编写同一代码的方式,并考虑哪种方式看起来更漂亮:
is_valid = form!= null && form.is_valid(true)
is_valid = form is not None and form.is_valid(include_hidden_fields = True)
第二种方法 该示例读取的内容更像是自然的英语,并且显式包含参数名称可以更深入地了解其目的。 除了语言方面的考虑外,编码风格还可能受到相似的美感概念的影响。 例如,名称is_valid会问一个简单的问题,然后可以期望该方法以其返回值回答该问题。
诸如validate之类的名称将是模棱两可的,因为即使根本没有返回任何值,它也将是准确的名称。 但是,过于依赖美观作为设计决策的标准是很危险的。 如果考虑了其他理想,而您仍然剩下两个 可行的选择,当然要考虑将美观因素纳入方程式,但要确保首先考虑其他方面。 在达到这一点之前,您很可能会使用其他一些标准来找到一个不错的选择。
显式胜于内隐
尽管这个概念似乎似乎更容易解释,但实际上是要遵循的棘手准则之一。
从表面上看,这似乎很简单:不要执行程序员未明确命令的任何操作。
除了Python本身之外,框架和库也负有类似的责任,因为它们的代码将由其他程序员访问,而他们的目标并不一定总是事先知道的。
不幸的是,从内存管理到显示例程,真正的显式代码必须考虑程序执行的每个细微差别。
某些编程语言确实希望程序员能提供如此详细的信息,但Python却没有。 为了使程序员的工作更轻松,并允许您 为了专注于眼前的问题,需要进行一些权衡。
通常,Python要求您显式声明您的意图,而不是发出使该意图变为现实的所有必要命令。
例如,在为变量分配值时,您无需担心预留必要的内存,为该值分配指针以及在不再使用该内存时对其进行清理。 内存管理是变量分配的必要部分,因此Python会在后台处理它。
显式声明意图足以证明隐式行为是合理的。 相反,只要找到匹配项,Perl编程语言中的正则表达式就会自动将值分配给特殊变量。 某些不熟悉Perl处理这种情况的方式的人不会理解依赖于它的代码片段,因为变量似乎是凭空产生的,并且没有与之相关的赋值。 Python程序员尽量避免这种类型的隐式行为有利于写出更具可读性的代码。
由于不同的应用程序具有不同的声明意图的方式,因此没有一个通用的解释将适用于所有情况。 取而代之的是,该指南会在整本书中频繁出现,阐明了如何将其应用于各种情况。
tax = .07 #make a variable named tax that is floating point
print (id(tax)) #shows identity number of tax
print("Tax now changing value and identity number")
tax = .08 #create a new variable, in a different location in memory
# and mask the first one we created
print (id(tax)) # shows identity of tax
print("Now we switch tax back...")
tax = .07 #change tax back to .07 (mask the second one and reuse first
print (id(tax)) #now we see the original identity of tax
简单要胜于复杂
这是一个更为具体的指导方针,主要涉及接口的设计 框架和库。
此处的目标是使界面尽可能简单明了,并充分利用程序员对现有界面的了解。
例如,缓存框架可以使用与标准字典相同的接口,而不是发明一套全新的方法调用。 当然,此规则还有许多其他应用,例如利用以下事实:大多数表达式无需显式测试就可以计算为真或假。 例如,以下两行代码在功能上对于字符串是相同的,但是请注意它们之间的复杂性差异:
if value is not None and value != ":
if value:
如您所见,第二个选项要简单得多
阅读并理解第一个示例中涵盖的所有情况,无论如何都将评估为假,因此更简单的测试同样有效,
它还有另外两个好处:
运行速度更快,执行的测试更少,并且可以在更多情况下工作,因为单个对象可以定义他们自己的方法来确定应该评估为真还是假。
似乎这是一个令人费解的例子,但这只是经常出现的那种事情。 通过依赖更简单的界面,您通常可以在生成更易读的代码的同时利用优化和增加的灵活性。
复杂胜于复杂
但是,有时为了完成工作需要一定程度的复杂性。
例如,数据库适配器无法使用简单的字典式界面,而是需要大量对象和方法来覆盖其所有功能。
在这种情况下要记住的重要一点是,复杂性并不一定要求它变得复杂。
显然,这一点的棘手之处在于区分两者。 每个术语的字典定义经常相互引用,这大大模糊了两者之间的界线。
出于本指南的考虑,大多数情况倾向于对以下两个术语采取以下观点:
•Complex:由许多相互连接的部分组成
•Complicated:如此复杂以至于难以理解因此面对需要跟踪大量的事物,保持尽可能的简单甚至更为重要。
这可以采取将方法合并到较少数量的对象上的形式,可以将对象分组为更具逻辑性的排列,甚至可以简单地确保使用有意义的名称,而不必深入研究代码来理解它们。
平坦比嵌套更好
最初,该指南似乎没有意义,但它与结构的布局有关。 有问题的结构可能是对象及其属性,包及其包含的模块,甚至是功能内的代码块。 目的是使事物尽可能保持与同伴的关系,而不是父母和孩子的关系。 对于 例如,使用以下代码段:
if x > 0:
if y > 100:
raise ValueError("Value for y is too large.")
else:
return y
else:
if x == 0:
return False
else:
raise ValueError("Value for x cannot be negative.")
在此示例中,要跟踪实际发生的事情相当困难,因为代码块的嵌套性质要求您跟踪多个级别的条件。 考虑以下替代方法来编写相同的代码,将其变平:
x=1
y=399 # change to 39 and run a second time
def checker(x,y):
if x > 0 and y > 100:
raise ValueError("Value for y is too large.")
elif x > 0:
return y
elif x == 0:
return False
else:
raise ValueError("Value for x cannot be negative.")
print(checker(x,y))
放在一个函数中并进行了展平,您可以看到遵循第二个示例中的逻辑要容易得多,因为所有条件都处于同一级别。
它甚至可以避免两行代码,从而节省了两行代码。
虽然这个想法通常在编程中很常见,但这实际上是存在elif关键字的主要原因;
Python对缩进的使用意味着复杂的if块会很快失控。 使用elif关键字,在Python中没有C ++或VB.NET中的switch或select大小写结构。
为了处理需要多重选择结构的问题,Python根据情况使用了一系列if,elif,elif等。
有PEP建议包含开关类型的结构。 但是,没有一个成功。
警告:可能不太明显的是,此示例的重构最终对x> 0进行了两次测试,而以前仅执行过一次。
如果该测试是一项昂贵的操作(例如数据库查询),则以这种方式进行重构会降低程序的性能,因此这是不值得的。 稍后将详细介绍
准则,“实用胜过纯度”。
对于包布局,平面结构通常可以允许一次导入,以使整个包在单个名称空间下可用。
否则,程序员将需要知道完整的结构才能找到所需的特定类或函数。 有些软件包太复杂了,以至于嵌套结构可以帮助减少每个命名空间上的混乱情况,但是最好仅在出现问题时才开始进行平面嵌套。
稀疏胜于密集
该原则很大程度上与Python源代码的外观有关,它有利于使用空格来区分代码块。 目标是将高度相关的代码片段保持在一起,同时将它们与后续或无关的代码分开,而不是简单地将所有代码片段一起运行以节省磁盘上的几个字节。
那些熟悉Java,C ++和其他使用{}
表示语句块的语言的人也知道,只要语句块位于 在大括号内,空格或缩进仅具有可读性值,并且对代码执行没有影响。
在现实世界中,有许多需要解决的特定问题,例如如何分离模块级类或如何处理单行if块。 尽管没有一套适用于所有项目的规则,但是PEP 82确实指定了源代码布局的许多方面,可帮助您遵守此原则。 它提供了许多有关如何格式化导入语句,类,函数甚至许多类型的表达式的提示。 有趣的是,PEP 8包含许多有关表达式的规则,特别是鼓励避免多余的空格。 采取以下示例,这些示例直接来自PEP 82
Yes: spam(ham[1], {eggs: 2})
No: spam( ham[ 1 ], { eggs: 2 } )
Yes: if x == 4: print x, y; x, y = y, x
No: if x == 4 : print x , y ; x , y = y , x
Yes: spam(1)
No: spam (1)
Yes: dict['key'] = list[index]
No: dict ['key'] = list [index]
这种明显差异的关键在于 空格是宝贵的资源,应负责任地分配。 毕竟,
如果一切都试图以一种特定的方式脱颖而出,那么什么也没有真正脱颖而出。
如果您使用空格来分隔甚至是高度相关的代码位(例如上述表达式),则真正无关的代码与其余代码没有什么不同。
那可能是该原则最重要的部分,也是将其应用于代码设计其他方面的关键。
在编写库或框架时,通常最好定义一小组独特的对象和接口,这些对象和接口可以在整个应用程序中重复使用,并在适当的地方保持相似性,并区分其余部分。
可读性至关重要
最后,我们有一个Python世界上每个人都可以落后的原则,但这主要是因为它是整个领域中最模糊的一个 采集。 从某种意义上讲,它以一个巧妙的方式总结了整个Python哲学,但是它还有很多不确定性,值得进一步研究。 可读性涵盖了广泛的问题,例如模块的名称,类,函数和变量。 它包括各个代码块的样式以及它们之间的空白。 如果可以做到这一点,甚至可以涉及多个职能或多个类别之间的责任分离,以使人眼更容易理解。
这才是真正的要点:代码不仅可以被计算机读取,还可以由必须维护的人读取。 与编写新代码相比,这些人阅读新代码的频率要高得多,而且通常是别人编写的代码。 可读性就是积极促进人类对代码的理解。
从长远来看,当涉及的每个人都可以轻松地打开文件并轻松地进行开发时,开发会容易得多 了解其中发生了什么。
这在高流动性的组织中似乎是理所当然的,新程序员必须定期阅读其前任的代码,但对于那些不得不在编写代码几周,几个月甚至几年后才阅读自己的代码的人来说,也是如此。
一旦我们失去了最初的思路,我们仅需提醒我们的是代码本身,因此花点时间使它易于阅读非常有价值。
另一个好的做法是在代码中添加注释。 经过足够的时间后,即使您忘记了自己的尝试或意图,它也不会受到损害,甚至可以为原始程序员提供帮助。 最好的部分是经常花费很少的额外时间。 可以简单地在两个函数之间添加空白行,或者为名词和动词命名变量。 但是,实际上,这更像是一套框架而不是一套规则。 注重可读性要求您始终像人类一样看待代码, 而不是仅像计算机那样。
记住黄金法则:为他人做您想让他们为您做的事。
可读性是散布在整个代码中的随机行为。
特殊情况还不足以打破规则
正如“可读性很重要”是我们始终应如何处理代码的标志性短语一样,该原则也关乎我们必须遵循的信念。
在大多数情况下都正确无误是一件好事,但只需要一小段代码就能破坏所有辛苦的工作。 不过,关于此规则的最有趣之处在于,它不仅与可读性有关,也与代码的任何其他方面无关。 无论决策是什么,实际上都是坚定地支持您所做的决定。 如果您致力于向后兼容性,国际化,可读性或其他任何方面,请不要仅仅因为出现了一项新功能而使这些承诺变得简单,就不能违反这些承诺。
实用性胜过纯洁
在这里,事情变得棘手。 以前的原则鼓励您始终做正确的事,而不管一种情况有多特殊,只要在正确的事变得困难时,这种情况似乎都会允许例外。 但是,实际情况要复杂一些,值得讨论。
到现在为止,它看起来一目了然:最快,最高效的代码可能并不总是最易读的,因此您可能必须接受低于标准的性能才能获得易于维护的代码。
在许多情况下确实是这样,并且Python的许多标准库在原始性能方面都不理想,而是选择了对其他环境(如Jython或IronPython)更具可读性和可移植性的纯Python实现。
但是,在更大范围内,问题要深得多。 在任何级别设计系统时,都很容易进入低头模式, 您将重点放在手头的问题以及如何最好地解决它上。 这可能涉及算法,优化,接口方案,甚至甚至是重构,但通常归结为一件事情很难做,以至于您暂时不会看大图。
在那种模式下,程序员通常会在当前上下文中做最好的事情,但是当为了更好的外观而退缩时,这些决定与应用程序的其余部分不符。
目前不容易知道该走哪条路。 您是否尝试优化应用程序的其余部分以匹配您刚刚编写的完美例程? 您是否重写了原本完美的功能,以期获得一个更具凝聚力的整体? 还是只留下不一致之处,希望它不会使任何人绊倒?
答案通常与情况有关,但是其中一种选择通常会比其他选择更为实际。 通常,最好保持更大的整体一致性 以牺牲一些可能不理想的小区域为代价。
同样,Python的大多数标准库都使用这种方法,但是也有例外。 需要大量计算能力或在需要避免瓶颈的应用程序中使用的软件包通常会以C语言编写,以提高性能,但以可维护性为代价。 然后需要将这些程序包移植到其他环境,并在不同的系统上进行更严格的测试,但是获得的速度比更纯的Python实现所允许的目的更实际。
错误永远不会静默传递
Python支持强大的错误处理系统,提供了许多内置的内置异常,但是人们常常怀疑何时应该使用这些异常以及何时需要新的异常。 Python Zen的这一行提供的指导非常简单,但是与其他许多语言一样,其表面还有很多其他内容。
第一个任务是 阐明错误和异常的定义。 尽管这些词(像计算机世界中的许多其他词一样)常常被附加了很多其他含义,但将它们作为通用语言使用时,具有一定的价值。 考虑一下《韦氏词典》中发现的以下定义:
•与行为守则无知或轻率偏离的行为或条件
•不适用规则的情况这里省略了这些术语以帮助说明
这里省略了这些术语,以帮助说明这两个定义的相似程度。在现实生活中,这两个术语之间观察到的最大差异是由偏离规范引起的问题的严重性。异常通常被认为具有较小的破坏性,因此更容易被接受,但是异常和错误的含义相同:违反某种期望。
为了便于讨论,术语“例外”将用于指代任何此类偏离规范的行为。
注意:要意识到的一件事是,并非所有异常都是错误。有些用于增强代码流选项,例如使用StopIteration。
在代码流用法中,异常提供了一种方法来指示函数内部发生的事情,即使该指示与其返回值无关。这种解释使得不可能单独描述异常。必须将它们放在期望的上下文中 可能会被违反。 每次编写一段代码时,我们都会保证它将以特定的方式工作。 异常破坏了承诺,因此我们需要了解我们做出的承诺类型以及如何兑现承诺。 采取以下简单的Python函数并查找可能会被破坏的承诺:
def validate(data):
if data['username'].startswith('_'):
raise ValueError("Username must not begin with an underscore.")
这里最明显的承诺是validate()方法的承诺:如果传入的数据有效,则该函数将静默返回。
违反该规则(例如以下划线开头的用户名)将被明确地视为异常,很好地说明了这种不允许错误静默传递的做法。
引发异常会引起注意,并为调用此函数的代码提供足够的信息以了解发生了什么。
棘手的一点是查看可能引发的其他异常。例如,如果数据字典不包含用户名键(如功能所期望的那样),Python会引发KeyError。如果该键确实存在,但其值不是字符串,则Python在尝试访问startswith()方法时将引发AttributeError。如果数据根本不是字典,Python会引发TypeError。
这些假设中的大多数是正确操作的真实要求,但并非全部都必须如此。让我们假设这个验证 可以从许多上下文中调用函数,其中某些上下文甚至可能没有要求提供用户名。
在这种情况下,缺少用户名实际上根本不是例外,而只是需要考虑的另一种流程。
考虑到这一新要求,可以对validate()进行一些更改,使其不再依赖用户名密钥的存在来正常工作。 但是,所有其他假设应保持不变,并且在违反时应提出各自的例外情况。 更改后的外观如下。
def validate(data):
if 'username' in data and data['username'].startswith('_'):
raise ValueError("Username must not begin with an underscore.")
就像这样,一个假设已被删除,该函数现在可以正常运行而无需在数据字典中提供用户名。或者,您现在可以明确检查缺少的用户名,并在确实需要的情况下引发更具体的异常。其余异常的处理方式取决于调用validate()的代码的需求,并且有一种补充原则可以处理这种情况。
除非明确沉默
像其他任何支持异常的语言一样,Python允许触发异常的代码捕获异常并以不同的方式处理它们。在前面的验证示例中,验证错误可能比完整的回溯更好地向用户显示。考虑一个小的命令行程序,该程序接受用户名作为参数,并根据先前定义的规则对其进行验证:
import sys
def validate(data):
if 'username' in data and data['username'].startswith('_'):
raise ValueError("Username must not begin with an underscore.")
if __name__ == '__main__':
username = sys.argv[1]
try:
validate({'username': username})
except (TypeError, ValueError) as e:
print (e)
#out of range since username is empty and there is no
#second [1] position
在本示例中,用于捕获异常并将其存储为变量e的语法首先在Python 3.0中可用。在此示例中,所有可能引发的异常都将被该代码捕获,并且仅消息将显示给用户,而不是完整的回溯。这种错误处理形式使复杂的代码可以使用异常来表示违反预期的要求,而无需删除整个程序。