计算机科学中最困难的两个问题是命名事物、缓存失效引起错误."这个经典的笑话,出自利昂·班布里克之手,并基于菲尔·卡尔顿的一句话,包含了一个真理的核心:很难为变量、函数、类和编程中的任何其他东西想出一个好名字,正式的名称是标识符。简洁、描述性的名称对于程序的可读性非常重要。但是创造名字说起来容易做起来难。如果你要搬到一个新房子,把你所有的搬家箱子都贴上“东西”的标签是简洁的,但不具有描述性的,你不知道箱子里具体装的什么东西。一本编程书籍的描述性名称可能是“用 Python 开发的电脑游戏”,但这并不简洁。
除非你正在编写一次性的程序,之后就不打算再进行代码维护,否则你应该考虑在程序中选择好的名字。如果你简单地使用a
、b
和c
作为变量名,你以后要维护代码时就要多花些时间努力去回忆这些变量最初是用来做什么的。
名字是你必须做出的主观选择。一个自动化的格式化工具,比如第三章描述的 Black,不能帮你给你的变量取名。这一章为你提供了一些指导,帮助你选择合适的名字,避免不合适的名字。和往常一样,这些指导方针并不是一成不变的:你自己拿捏好何时将它们应用到你的代码中。
元语法变量
当我们需要一个通用的变量名时,我们通常在教程或代码片段中使用一个元语法变量。在 Python 中,我们经常在一些不注重变量名的代码片段中见到如下变量名spam
、eggs
、bacon
和ham
。这就是为什么本书在代码示例中使用这些名称;它们并不意味着您可以在实际的程序中使用它们。这些名字来自于蒙蒂·派森的《垃圾邮件》小品en.wikipedia.org/wiki/Spam_(Monty_Python)
。名称foo
和bar
也是元同步变量的常见名称。这些源自 FUBAR,这是二战时期美国陆军俚语的首字母缩略词,表示“乱七八糟”。
变量名风格
因为 Python 标识符区分大小写,并且不能包含空白,所以程序员对包含多个单词的标识符使用以下几种风格:
- 用下划线分隔单词,下划线在每个单词之间看起来像一条扁平的蛇。这种情况通常意味着所有的字母都是小写的,尽管常量通常是用
UPPER_SNAKE_CASE
写的。 - 通过在第一个单词后大写每个单词的开头来分隔单词。这种情况通常意味着第一个单词以小写字母开头。大写字母看起来像骆驼的驼峰。
PascalCase
,因其在 Pascal 编程语言中的使用而得名,与camelCase
相似,但也将第一个单词大写。
大小写是一个代码格式问题,我们将在第 3 章中讨论。最常见的风格有snake_case
和camelCase
。只要您的项目始终使用其中一种,而不是两种都使用,那么使用任何一种都可以。
PEP8 的命名惯例
第 3 章中介绍的 PEP8 文档对 Python 命名约定有一些建议:
- 所有字母都应该是 ASCII 字母,即没有重音符号的大写和小写英文字母。
- 模块应该用短的,全部小写的名字。
- 类名要使用
PascalCase
风格。 - 常量变量要用大写
SNAKE_CASE
风格。 - 函数、方法和变量名应该用小写
snake_case
书写。 - 方法的第一个参数应该总是用小写字母命名
self
。 - 类方法的第一个参数应该总是用小写字母命名
cls
。 - 类中的私有属性应该总是以下划线(
_
)开头。 - 类中的公共属性不应该以下划线(
_
)开头。
你可以根据需要改变或打破这些规则。例如,尽管英语是编程中的主导语言,但您可以使用任何语言中的字母字符作为标识符: コンピューター = 'laptop'
是语法上有效的 Python 代码。正如你在这本书里看到的,我对变量名的偏好违背了 PEP8,因为我用的是camelCase
而不是snake_case
。PEP8 包含一个提醒,程序员不需要严格遵循 PEP8。重要的可读性因素不是你选择哪种风格,而是使用该风格的一致性。
你可以在www.python.org/dev/peps/pep-0008/#naming-conventions
在线阅读 PEP8 的“命名约定”部分。
适当的名称长度
显然,名字不能太长或太短。长变量名输入起来很繁琐,而短变量名可能会令人困惑或难以理解。因为代码被阅读的次数比被编写的次数多,所以宁可选择过长的变量名更加妥当一些。我们来看一些名字太短和太长的例子。
名字太短
最常见的命名错误是选择太短的名字。当你第一次写名字的时候,简短的名字通常对你来说是有意义的,但是几天或几周后,它们的确切含义可能会丢失。让我们考虑几种类型的简称。
- 像
g
这样的一两个字母的名字可能指的是其他一些以g
开头的单词,但是有很多这样的单词。只有一两个字母的首字母缩写和名字对你来说很容易写,但对其他人来说却很难读懂。 - 一个像
mon
一样的缩写名,可以代表监控器、月份、怪兽、或者任何数量的单词。 - 一个像
start
一样的单字名可以是含糊的:什么的开始?这样的名字可能缺少上下文,当被其他人阅读时不容易看出来。
一个或两个字母、缩写或单个单词的名称对您来说可能是可以理解的,但是您始终需要记住,其他程序员(甚至几周后的您)将很难理解它们的含义。
有一些例外情况,短变量名是可以的。例如,在遍历一系列数字或列表索引的for
循环中,通常使用i
(代表索引)作为变量名,如果有嵌套循环,则使用j
和k
(因为它们在字母表中位于i
之后):
>>> for i in range(10): ... for j in range(3): ... print(i, j) ... 0 0 0 1 0 2 1 0 `--snip--`
另一个例外是将x
和y
用于笛卡尔坐标。在大多数其他情况下,我警告不要使用单字母变量名。虽然用w
和h
作为width
和height
的简写,或者用n
作为number
的简写可能很有诱惑力,但是这些意思对其他人来说可能并不明显。
不要从你的源代码中删除字母
虽然像memcpy
(内存复制)和strcmp
(字符串比较)这样的名字中的首字母在 20 世纪 90 年代之前的 C 编程语言中很流行,但它们是一种不可读的命名风格,你今天不应该使用。如果一个名字不容易发音,它就不容易理解。
此外,请随意使用短语,使您的代码看起来像简单的英语。比如number_of_trials
比单纯的number_trials
可读性更强。
名字太长
一般来说,名字的范围越大,描述性就应该越强。像payment
这样的短名字对于单个短函数中的局部变量来说是合适的。但是如果您在一个 10,000 行的程序中使用它作为一个全局变量,那么payment
可能不够具有描述性,因为这样一个大型程序可能会处理多种支付数据。更具描述性的名字,如salesClientMonthlyPayment
或annual_electric_bill_payment
,可能更合适。名称中的附加单词提供了更多的上下文并消除了歧义。
描述过度总比描述不够好。但是有指导方针来决定什么时候长名字是不必要的。
名称中的前缀
在名称中使用常见的前缀可能表示名称中不必要的细节。如果一个变量是一个类的属性,前缀可能提供不需要在变量名中的信息。例如,如果你有一个带有weight
属性的Cat
类,很明显weight
指的是猫的体重。所以catWeight
这个名字画蛇添足了,而且不必要的长。
类似地,一个旧的现在已经过时的实践是使用匈牙利符号在名称中包含数据类型缩写的实践。比如名字strName
表示变量包含字符串值,iVacationDays
表示变量包含整数。现代语言和 ide 可以将这种数据类型信息传递给程序员,而不需要这些前缀,这使得匈牙利符号在今天已然过时。如果您发现您的名称中包含了某个数据类型的名称,请考虑将其移除。
另一方面,包含布尔值的变量或者返回布尔值的函数和方法的前缀is
和has
使得这些名称更易读。考虑以下名为is_vehicle
的变量和名为has_key()
的方法的使用:
`if item_under_repair.has_key('tires'):` `is_vehicle = True`
has_key()
方法和is_vehicle
变量支持对代码的简单英文解读:“如果正在维修的项目有一个名为‘轮胎’的键,那么这个项目就是一辆汽车。”
类似地,在你的名字中添加单位可以提供有用的信息。存储浮点值的weight
变量是不明确的:重量是以磅、千克还是吨为单位?这个单元信息不是数据类型,所以包含前缀或后缀kg
或lbs
或tons
与匈牙利符号不同。如果您没有使用包含单位信息的特定于体重的数据类型,将变量命名为类似于weight_kg
的名称可能是谨慎的。事实上,在 1999 年,当洛克希德·马丁公司提供的软件使用英制标准单位进行计算时,火星气候轨道器机器人太空探测器丢失了,而美国宇航局的系统使用公制,导致了不正确的轨迹。据报道,该航天器耗资 1.25 亿美元。
名称中的连续数字后缀
名称中的连续数字后缀表示您可能需要更改变量的数据类型或为名称添加不同的细节。数字本身通常不能提供足够的信息来区分这些名字。
像payment1
、payment2
和payment3
这样的变量名不能告诉阅读代码的人这些支付值之间的区别。程序员可能应该将这三个变量重构为一个名为payments
的列表或元组变量,其中包含三个值。
具有类似于makePayment1(amount)
、makePayment2(amount)
等调用的函数可能应该被重构为一个接受整数参数的函数:makePayment(1, amount)
、makePayment(2, amount)
等等。如果这些函数有不同的行为来证明独立的函数,那么这些数字背后的含义应该在名字中说明:例如:makeLowPriorityPayment(amount)
和makeHighPriorityPayment(amount)
,或者make1stQuarterPayment(amount)
和make2ndQuarterPayment(amount)
。
如果你有一个合理的理由选择带有连续数字后缀的名字,使用它们是没问题的。但是如果你使用这些名字是因为这是一个容易做出的选择,考虑修改它们。
使名字可搜索
除了小程序之外的所有程序,你可能需要使用你的编辑器或者 IDE 的Ctrl+F
“查找”特性来定位你的变量和函数被引用的位置。如果你选择一个短的通用变量名,比如num
或a
,你将会得到几个错误的匹配结果。为了使名称易于立即找到,可以使用包含特定细节的较长变量名来形成唯一的名称。
一些 ide 具有重构特性,可以根据程序使用名字的方式来识别名字。例如,一个共同的特性是“重命名”工具,它可以区分名为num
和number
的变量,以及局部num
和全局num
变量。但是您仍然应该选择名称,就好像这些工具不可用一样。
记住这条规则自然会帮助你选择描述性的名字,而不是一般的名字。email
这个名字很模糊,所以考虑一个更具描述性的名字,比如emailAddress
、downloadEmailAttachment
、emailMessage
或replyToAddress
。这样的名称不仅更准确,而且在源代码文件中也更容易找到。
避免玩笑、双关语和特殊文化
在我之前的一份软件工作中,我们的代码库包含一个名为gooseDownload()
的函数。我不知道这意味着什么,因为我们正在创造的产品与鸟类或下载鸟类毫无关系。当我找到最初编写这个函数的更资深的同事时,他解释说goose
是一个动词,意思是“推动引擎”,我也不知道这句话是什么意思。他不得不进一步解释“推动引擎”是汽车术语,意思是踩下油门让引擎跑得更快。因此,gooseDownload()
是一个让下载速度更快的函数。我点点头,回到我的办公桌。多年后,在这位同事离开公司后,我将他的职能重新命名为increaseDownloadSpeed()
。
在程序中选择名称时,您可能会尝试使用笑话、双关语或特殊文化来为代码添加一些轻松的元素。不要这样。笑话可能很难在文本中传达,这个笑话将来可能不会那么有趣了。双关语也很容易被忽略,处理来自同事的重复错误报告,他们会把双关语误认为是打字错误,这是非常令人头疼的。
特定于区域性的引用可能会妨碍清楚地传达代码的意图。互联网使得与世界各地的陌生人分享源代码变得前所未有的容易,这些陌生人不一定精通英语,也不一定听得懂英语笑话。正如本章前面提到的,Python 文档中使用的名称spam
、eggs
和bacon
引用了一个 Monty Python 喜剧小品,但我们仅将它们用作 metasyntactic 变量;在现实世界的代码中使用它们是不明智的。
最好的策略是以非英语母语者容易理解的方式编写代码:礼貌、通俗、严谨。我以前的同事可能认为gooseDownload()
是一个有趣的笑话,但如果失去了可解释性,这个笑话也就没有什么意义了。
不要覆盖内置名称
你也不应该对你自己的变量使用 Python 的内置名称。例如,如果你命名一个变量为list
或set
,你将覆盖 Python 的list()
和set()
函数,这可能会导致你的代码出现错误。list()
函数创建列表对象。但是覆盖它会导致以下错误:
>>> list(range(5)) [0, 1, 2, 3, 4] >>> list = ['cat', 'dog', 'moose'] # 1 >>> list(range(5)) # 2 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'list' object is not callable
如果我们给名称list
1 分配一个列表值,我们将失去原来的list()
函数。试图调用list()
2 会导致TypeError
。要了解 Python 是否已经在使用一个名称,可以在交互式 Shell 中键入它,或者尝试导入它。如果这个名字没有被使用,你会得到一个NameError
或者ModuleNotFoundError
。例如,Python 使用名称open
和test
,但没有使用spam
和eggs
:
>>> open <built-in function open > >>> import test >>> spam Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'spam' is not defined >>> import eggs Traceback (most recent call last): File "<stdin>", line 1, in <module> ModuleNotFoundError: No module named 'eggs'
一些常见的被改写的 Python 名字有all
、any
、date
、email
、file
、format
、hash
、id
、input
、list
、min
、max
、object
、open
、random
、set
、str
、sum
、test
和type
。不要使用这些名字作为你的标识符。
另一个常见的问题是给你的.py
文件命名与第三方模块同名。例如,如果您安装了第三方 pyperclip 模块,但还创建了一个pyperclip.py
文件,则import pyperclip
语句会导入pyperclip.py
而不是 Pyperclip 模块。当你试图调用 Pyperclip 的copy()
或paste()
函数时,你会得到一个错误,说它们不存在:
>>> # Run this code with a file named pyperclip.py in the current folder. >>> import pyperclip # This imports your pyperclip.py, not the real one. >>> pyperclip.copy('hello') Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: module 'pyperclip' has no attribute 'copy'
注意覆盖 Python 代码中的现有名称,尤其是当您意外地得到这些has no attribute
错误消息时。
有史以来最糟糕的变量名
名称data
是一个糟糕的通用变量名,因为实际上所有变量都包含数据。命名变量var
也是一样,有点像给你的宠物狗取名“狗”。temp
这个名字对于临时保存数据的变量来说很常见,但仍然是一个糟糕的选择:毕竟,从长远的角度来看,所有变量都是临时的。不幸的是,尽管这些名字模糊不清,却经常出现;请避免在代码中使用它们。
如果您需要一个变量来保存温度数据的统计方差,请使用名称temperatureVariance
。不言而喻,名称tempVarData
将是一个糟糕的选择。
总结
选择名字与算法或计算机科学无关,但它是编写可读代码的重要部分。最终,变量的命名由您自己决定,但是请注意存在的许多准则。PEP8 文档推荐了几种命名约定,比如模块的小写名称和类的PascalCase
名称。名字不应该太短或太长。但是,宁可描述得太详细,也不要描述得不够详细。
名字应该简洁但有描述性。使用Ctrl+F
搜索功能很容易找到的名称是独特的描述性变量的标志。想想你的名字有多容易被搜索到,以此来判断你是否使用了一个过于普通的名字。此外,考虑一个英语比较菜的程序员是否能理解这个名字:避免在你的名字中使用笑话、双关语和文化参考;相反,选择有礼貌的,通俗的,严肃的名字。
虽然本章中的许多建议只是简单的指南,但是您应该总是避免使用 Python 的标准库已经使用的名称,例如all
、any
、date
、email
、file
、format
、hash
、id
、input
、list
、min
、max
、object
、open
、random
、set
、str
、sum
、test
和type
。使用这些名称可能会导致代码中出现细微的错误。
计算机不在乎你的名字是描述性的还是模糊的。名字使代码更容易被人类阅读,而不是更容易被计算机运行。如果你的代码是可读的,就很容易理解。如果容易理解,就容易改变。而且如果很容易改变,那么修复 bug 或者增加新功能就更容易了。使用容易理解的名字是生产高质量软件的基础步骤。