Python 代码风格

1 原则

在开始讨论Python社区所采用的具体标准或是由其他人推荐的建议之前,考虑一些总体原则非常重要。

请记住可读性标准的目标是提升可读性。这些规则存在的目的就是为了帮助人读写代码,而不是相反。

本小节讨论你所需记住的一些原则。

1.1 假定你的代码需要维护

人们很容易倾向相信某时所完成的工作在未来不需要添加一部分或对其维护。这是由于很难预料到未来的需求,以及低估自己造成Bug的倾向。然而,所写代码很少不被修改一直存在。

如果你假设自己所写代码会“一劳永逸”的无需之后进行阅读、调试或修补,那么你就会非常容易陷入忽视其他可读性原则的境地,这仅仅是因为你相信“这次并不重要”。

因此,保持对自己感觉所写代码无需维护的直觉的不信任才是上策。稳赚不赔的办法是赌自己将会再次见到自己写的代码。即使你不维护,那也需要其他人维护。

1.2 保持一致性

一致性的两个方面分别为:内部一致性和外部一致性。

无论是从代码风格和代码结构层面来讲,代码都要尽量满足内部一致性。无论是哪种格式化规则,代码风格都要贯穿项目保持一致。代码结构的一致性也就是同样类型的代码放到一起。这样项目容易把控。

代 码还应该保持外部一致性。项目与代码的结构与其他人保持一致,如果一个新来的开发人员打开你的项目,你不应该让他的反应是:“我从来没见过像这样的东 西”。社区指导原则很重要,因为这就是开发人员加入到你的项目预期所见。类似的,以及相同的原因,请认真看待在使用特定框架时完成任务以及组织代码所采用 的标准。

1.3 考虑事物存在方式,尤其是带有数据

存在论(Ontology)的主要意思就是“关于存在的研究”。在哲学上(在该领域这个词很常用)存在论是关于现实与存在本质的研究,是形而上学的子集。

而对于写软件程序来说,存在论指的是关注不同“事物”在程序中如何存在。你如何将概念转化为在数据库中表示?亦或是用类结构表示?

这类问题最终影响你编写或组织代码的方式。是否使用继承或是组合来组织两个类之间的关系?数据库中用哪个表完成这项功能或是这个列属于谁?

这些建议最终归结为“在写代码之前先思考”。尤其是思考程序希望实现的目标,以及程序之间如何交互。程序是一个对象与数据交互的世界。那么,它们之间协作所需遵循的规则是什么?

1.4 不要做重复工作

当编写代码时,请考虑随着时间重复使用的值将会变更的情况。该值是否被用于多个模块或函数中?如果必要,需要花费多大代价修改它?

同样的原则适用于函数。你是否在程序中有大量的重复的代码?如果这些重复代码行数较多,可以考虑将其抽象到一个函数中,如果出现修改代码的需求,则更容易管理。

另一方面,对于该原则不要过犹不及。并不是所有值都需要在模块中定义为常量(这么做会损害可读性和可维护性)。请明智判断,不断问这样一个问题:“如果需要变更该代码,变更该代码的所有位置所需要的成本是多少”?

1.5 让注释讲故事

代码是一个故事。它是所发生故事的说明,在用户与程序交互过程中,从开始到结束。程序从某一点开始(可能带有一些输入),沿着一系列“选择自己的冒险故事”步骤到达终点,并结束(很可能带有一些输出结果)。

采用的注释风格可以是每一些行代码之前就添加一段注释,用于解释代码的功能。如果代码是一个故事,那么注释就是故事的解释与旁白。

如果叙事式注释做的很好,读者就可以通过阅读注释了解故事,从而解析代码(例如,当尝试解决问题或维护代码时),然后可以从零开始快速了解所需维护的代码,这样就可以专注于代码本身所代表的意义。

叙事式注释还可以帮助解释代码意图。它可以回答这样的问题:“写这段代码的人希望完成的目标是什么?”偶尔,还可以帮助回答问题:“为什么以这种方式完成工作”?这些问题是在你阅读代码时很自然会问的,为这些问题提供答案将会帮助了解这些内容。

因此,注释用于解释代码中不显而易见或复杂部分的原理。如果使用了有点复杂的算法,请考虑将指向解释模式文章的链接以及其他使用示例加入注释。

1.6 奥卡姆剃刀原则

编 写可维护代码最重要的原则通俗来讲就是奥卡姆剃刀原则:最简单的解决方案通常是最好的。在他的“Python之禅”博文 (https://www.python.org/dev/peps/pep-0020/),该页面是编程格言的集合(例如,在Python控制台中输入 “import this”就可以看到这篇),Tim Peters也包括了类似下面这句“如果你无法向人描述你的方案,那肯定不是一个好方案”。

上述原则在代码如何运行与代码外观层面都生效。当提到代码运行时,简单的系统更加容易维护。实现的简单化意味着更少引入复杂的BUG,哪些维护你代码的人(包括你自己)更容易凭直觉理解代码所代表的含义,并在不踩坑的前提下为程序增加代码。

至 于代码的外观,请记住,尽可能使得阅读代码就好像是在了解代码所做工作的故事,而不是为了解析词汇。词汇是手段,而故事才是最终目的。写一条诸如“不要使 用三元运算符”很容易。然而仅仅是遵循这些规则(虽然有价值)并不是代码明晰的充分条件。请专注以尽可能简洁的方式编写和组织代码

2 标准

Python社区大部分遵循所谓的PEP 8(https://www.python.org/ dev/peps/pep-0008/)指导原则,由Guido van Rossum(Python之父)编写并被包括Python标准库中的大多数主流Python项目采用。

PEP 8的普遍性是其强大的原因之一。该标准被大多数社区项目采纳,因此你可以预计大多数你遇到的Python代码都遵循该标准。当你以这种方式编写代码时,代码会更加容易阅读,也更容易编写。

2.1 简洁的规则

大多数PEP 8中的指导原则都很简单明了。部分重点如下:

l 使用4个空格缩进。不要使用制表符(\t)。

l 变量应该使用下划线连接,不使用骆驼式命名风格(使用my_var而不是myVar)。类名称以字母开头就是骆驼式命名风格(例如:MyClass)。

l 如果一个变量的用处是:“仅内部使用”,在变量名称之前加上下划线。

l 在运算符前后加上单空格(例如,x + y,不是x+y),也包含赋值运算符(z = 3而不是z=3),只有在关键字参数情况下不适用,在这种情况下,空格可以省略。

l 在列表和字典中省略不必要的括号,(例如: [1, 1, 2, 3, 5],而不是[ 1, 1, 2, 3, 5 ])。 请阅读Python代码风格指南获得更多示例以及有关这些规则的更多讨论。

2.2 文档字符串

请记住,在Python中,如果在一个函数或类中第一个语句是一个字符串,该字符串会自动赋值给一个特殊的“_doc_”变量,该变量在调用Help(和一些其他的类)时会被使用。

PEP 8 规定文档字符串(该名称可以被望文生义)是必须的。

"""Do X, Y, and Z, then return the result."""

该句子与作为描述的文档字符串的对比:

"""Does X, Y, and Z, then returns the result."""

如果文档字符串是一行,那么需要在类或函数体之前加空行。如果文档字符串有多行,则将结束的双引号单独放一行。

2.3 空行

空行用于逻辑分块。

PEP8规定“最高级”的类和函数定义之间有两个空行。

class A(object):
pass

class B(object):
pass

代码清单1.

PEP 8还规定除了最高级之外,类和函数的定义以一个空行分隔。

class C(object):

def foo(self):

pass

def bar(self):

pass 

代码清单2.

在函数或其他代码段中使用单空行分隔逻辑段是合理的。请考虑在逻辑段之前使用注释解释代码段的作用。

2.4 导入

Python允许绝对路径导入和相对路径导入。在Python2中,解释器会尝试相对导入,如果找不到路径,然后再尝试使用绝对导入。

在Python 3中,使用特殊语法标记相对当如----以(.)开头----“正常”的导入方式只会尝试相对路径。Python 3的语法在Python 2.6以后版本可以使用。除此之外你可以使用—“_future_”关闭隐式相对路径导入。

如果可能,尽量使用绝对路径导入。如果不得不使用相对路径,请使用显式导入风格。如果你为Python 2.6或 2.7编写代码,请考虑选择Python 3中的显式风格。

当导入模块时,每个模块单独占一行。

import os
import sys

代码清单3

然而,如果你从同一个模块中导入多个名称,当然可以将这些名称分组到一行中。

from datetime import date, datetime, timedelta

代码清单4

除此之外,虽然PEP 8并没有强制要求,考虑以包来源的方式将导入分组。对于每一组,按照字母表顺序排序。

另外,在导入时,请不要忘了使用as关键字给导入的内容起别名。

from foo.bar import really_long_name as name

代码清单5

这使得你可以简化被频繁使用的长名称或不规范命名的名称。

当导入被频繁使用且原始名称无论何种原因不规范时,别名就很有价值了。

另一方面,请记住当你这么做时,你会在你的模块中掩盖了原始名称,如果没必要使用别名,这会使得代码变得不清晰。无论是使用何种工具,要做到具体情况,具体分析。

2.5 变量

正如之前所提到的,变量名称使用下划线连接,而不要使用骆驼代码风格(例如,my_val而不是myVal)。除此之外,起一个具有描述性的名称同样重要。

通常情况下使用非常短的变量名称并不合适,虽然某些情况下这么做也能接受,比如在循环中的变量(例如,for k in mydict_item())。

避免命名的函数名称与Python语言中的常用名称重复,就算是解释器允许也不行。无论在任何情况下,都不要命名某个对象为sum或print。类似的,避免list或dict之类的名称。

如 果你必须要命名一个与Python类型与关键字同名的变量,惯例是在变量名称之后加下划线;相比修改名称的拼写来说,这么做更加可取。例如,如果你将一个 类作为参数传递给一个函数,那么参数名称应该为class_,而不是klass(一个例外是静态方法,按照惯例使用cls作为第一个参数)。

2.6 注释

注释应该使用英语写完整的句子(译者注:当然在国内这点值得商榷),放在相关的代码之前。正确使用首字母大写和语法,以及保证拼写正确。

同时,保证注释最新。如果代码变更,那么注释可能也需要随之变更。你应该不希望注释与代码表示的意思相反,这很容易导致混淆。

模块可能包含一个注释头,通常由版本控制系统生成,其中包含文件版本的信息。这使得发现文件被修改变得容易,尤其是在将模块分发给别人使用时。

2.7 行长度

Python代码风格最有争议(也是最常被拒绝使用的)的方面是对行长度的限制。PEP 8要求行长度不超过79个字符,文档字符串不超过72个字符。

该规则让很多开发人员感到沮丧,这些开发人员认为我们生活在一个27寸宽屏显示器的时代。GitHub是一个非常流行共享代码的网站,所使用的窗口宽度是120个字符。

而该规则的支持者指出很多人依然使用窄屏或80字符长度的终端,甚至仅仅是将代码窗口的宽度设置为小于屏幕宽度。

争论很难有一个结果。总之,无论是遵循79字符宽度的标准或是更宽的标准,你应该按照项目标准的规范编码。当行长度过长时,你应该知道如何处理代码。

使用圆括号是封装单行长代码的最佳方式,如下所示:

if (really_long_identifier_that_maybe_should_be_shorter and
other_really_long_identifier_that_maybe_should_be_shorter):
do_something()

代码清单6

只要可能,使用该方法,而不是在换行符之前使用 \字符。注意在使用诸如and之类的操作符时,尽可能将其置于换行符之前。

封装函数调用也是可以的。PEP 8列出了许多可接受的方式完成封装。一般规则是使得同级别行缩进保持一致。

really_long_function_name(

categories=[

x.y.COMMON_PHRASES,

x.y.FONT_PREVIEW_PHRASES,

],

phrase='The quick brown fox jumped over the lazy dogs.',

)

代码清单7

当在函数调用、列表或字典中分行时,在行结尾部分添加逗号。

3 小结

大多数时候,一年后阅读你代码的人就是你自己。记忆并不像一开始时那么好用。在编写代码时没有留心代码的可读性与可维护性自然会使得代码难以阅读和维护。

通观本书,你学会了如何使用Python中多种模块、类与结构。当需要决定如何解决问题时,请记住调试代码比写代码更有技术含量。

因此,以代码尽可能简洁和可读为目标。一年后的你将会感谢自己。当然,你的同事以及下属也会感谢你。

------本篇结选自本人翻译的书《Python 高级编程》, 清华大学出版社-------------------------------------------------------------------------------------------------

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏西二旗一哥

Python - 编码问题

1474
来自专栏黑泽君的专栏

面向对象思想的设计原则概述

在实际的开发中,我们要想更深入的了解面向对象思想,就必须熟悉前人总结过的面向对象思想的设计原则。

1091
来自专栏Golang语言社区

【翻译】为什么 goroutine 的栈内存无穷大?

一些 Go 语言的新学习者总是会对 goroutine 栈内存占用大小感到非常好奇。这一般是由于程序员进行无限的函数循环调用导致的。为了说明这个问题,请思考以下...

3626
来自专栏python学习路

一、代码风格 1、假定你的代码需要维护2、保持一致性3、考虑对象在程序中存在的方式,尤其是那些带有数据的对象4、不要做重复工作5、让注释讲故事6、奥卡姆剃刀原则1、简洁的规则2、文档字符串3、空行4、

刚开始学的时候就要注意编码规范了,所以整理了一下,以便养成一个编码好习惯。不然以后真的不好改。 代码被读的次数远大于被写的次数。 作为一名程序员(使用任何语言)...

2455
来自专栏ImportSource

必懂的NoSQL理论-Map-Reduce(中)

本文主要内容:分区和归并 上一文:必懂的NoSQL理论-Map-Reduce(上) Partitioning and Combining 分区和归并 在最简单...

3366
来自专栏Python入门

你还在为Python中文乱码而感到烦恼?今天老司机给你讲讲!

有没有遇到过这样的问题,读取文件被提示“UnicodeDecodeError”、爬取网页得到一堆乱码,其实这些都是编码惹的祸,如果不能真正理解编码的问题所在,就...

1873
来自专栏葡萄城控件技术团队

C# 8.0的三个值得关注的新特性

1353
来自专栏王清培的专栏

Redis 数据结构与内存管理策略(上)

Redis 数据结构与内存管理策略(上) 标签: Redis Redis数据结构 Redis内存管理策略 Redis数据类型 Redis类型映射 作者:王清培(...

3377
来自专栏C/C++基础

C++为什么要引入异常处理机制

在程序设计中,错误时不可避免的。及时有效的发现错误,并作出适当的处理,无论是在软件的开发阶段还是在维护阶段都是至关重要的。错误修复技术是提高代码健壮性的最有效的...

921
来自专栏禁心尽力

多线程之策略模式

今天给各位分享一种Java23种设计模式中最常见的设计模式--策略模式。为什么将策略模式和多线程绑在一起呢,不知道各位有没有注意过我们在进行多线程编程的时候,创...

2137

扫码关注云+社区

领取腾讯云代金券