美国网络电视频道曾经播出过一部名叫《灵异妙探》的侦探类喜剧。在该剧中,对细节观察入微的业余侦探肖恩·斯宾塞总是假装使用超能力来侦破案件。剧中吸引人的一幕是肖恩介绍老朋友的场景,他总能天马行空地想出一些姓名,如伽利略·汉普金斯、拉文德·库姆斯、讨厌鬼马文·巴尔内斯等。我对这些姓名留有深刻印象。我曾经在人口普查局看到过一份姓名列表,列表中的姓名与肖恩起的那些姓名类似,让我觉得十分新奇。
内容:
艾伦·唐尼(Allen Downey)在他的编写《像计算机科学家一样思考 Python(第 2 版)》中描述了两种类型的软件开发计划:“原型和补丁(Prototype and Patch)”和“按计划开发(Designed Development)”。在“原型和补丁”思想的指导下,先从一个简单的程序开始,然后使用补丁和可编辑的代码去解决测试过程中遇到的问题。在解决一个难以理解的复杂问题时,这不失为一个好办法,但是这会产生复杂且不可靠的代码。如果对问题本身有清晰的认识,并且知道如何去解决它,那么你就应该采用“按计划开发”的软件设计思想,尽量解决未来可能出现的问题,避免后续为程序添加补丁。这种方法可以使编写的代码更简单和有效,而且通常也会使代码变得更健壮和可靠。
伪代码是一种使用任何结构化的人类语言对计算机程序执行过程进行解释的高级非正式描述,它像是一种包含关键词和适当缩进的简单编程语言。程序开发者使用伪代码的主要目的是忽略所有编程语言中的复杂语法,从而专注于程序的底层逻辑。尽管伪代码被广泛使用,但是目前只存在一些伪代码使用方法的指导原则,而这些指导原则还没有形成正式的标准。如果编写程序时受挫,那么主要原因是你没有花时间去编写伪代码。对我来说,每当对编写代码感到困惑迷茫的时候,伪代码总能给我新的启发。
虚假姓名生成器程序的伪代码描述如下:
- 定义名字元组
- 定义姓氏元组
- 从名字元组中随机选择一个名字
- 将这个名字分配给变量
- 从姓氏元组中随机选择一个姓氏
- 将这个姓氏分配给变量
- 在屏幕上用红色字体按选择的顺序输出姓名
- 询问用户是退出程序,还是重新开始
- 如果用户选择重新开始:
- 重复上述过程
- 如果用户选择退出:
- 结束并退出程序
- # 首先,向程序导入 sys 模块和 random 模块。
- # sys 模块使你能够访问具体的系统错误消息,同时允许你把 IDLE 窗口中的输出文字设置为醒目的红色。
- # random 模块使你可以随机地从存放名字和姓氏的元组中选择数据项。
- import sys, random
-
- # print 语句的作用是向用户介绍本程序的功能。
- # 换行命令符\n 强制开始新行;在字符串输出的双引号中,不必使用\转义字符,而可以直接使用单引号’。
- # 需要注意的是,使用转义字符将会降低代码的可读性。
- print("Welcome to the Psych 'Sidekick Name Picker.'\n")
- print("A name just like Sean would pick for Gus:\n\n")
-
- # 接下来,分别定义名字和姓氏元组。
- first = ('Baby Oil', 'Bad News', 'Big Burps', "Bill 'Beenie-Weenie'",
- "Bob 'Stinkbug'", 'Bowel Noises', 'Boxelder', "Bud 'Lite' ",
- 'Butterbean', 'Buttermilk', 'Buttocks', 'Chad', 'Chesterfield',
- 'Chewy', 'Chigger", "Cinnabuns', 'Cleet', 'Cornbread', 'Crab Meat',
- 'Crapps', 'Dark Skies', 'Dennis Clawhammer', 'Dicman', 'Elphonso',
- 'Fancypants', 'Figgs', 'Foncy', 'Gootsy', 'Greasy Jim', 'Huckleberry',
- 'Huggy', 'Ignatious', 'Jimbo', "Joe 'Pottin Soil'", 'Johnny',
- 'Lemongrass', 'Lil Debil', 'Longbranch', '"Lunch Money"', 'Mergatroid',
- '"Mr Peabody"', 'Oil-Can', 'Oinks', 'Old Scratch',
- 'Ovaltine', 'Pennywhistle', 'Pitchfork Ben', 'Potato Bug',
- 'Pushmeet', 'Rock Candy', 'Schlomo', 'Scratchensniff', 'Scut',
- "Sid 'The Squirts'", 'Skidmark', 'Slaps', 'Snakes', 'Snoobs',
- 'Snorki', 'Soupcan Sam', 'Spitzitout', 'Squids', 'Stinky',
- 'Storyboard', 'Sweet Tea', 'TeeTee', 'Wheezy Joe',
- "Winston 'Jazz Hands'", 'Worms')
-
- last = ('Appleyard', 'Bigmeat', 'Bloominshine', 'Boogerbottom',
- 'Breedslovetrout', 'Butterbaugh', 'Clovenhoof', 'Clutterbuck',
- 'Cocktoasten', 'Endicott', 'Fewhairs', 'Gooberdapple', 'Goodensmith',
- 'Goodpasture', 'Guster', 'Henderson', 'Hooperbag', 'Hoosenater',
- 'Hootkins', 'Jefferson', 'Jenkins', 'Jingley-Schmidt', 'Johnson',
- 'Kingfish', 'Listenbee', "M'Bembo", 'McFadden', 'Moonshine', 'Nettles',
- 'Noseworthy', 'Olivetti', 'Outerbridge', 'Overpeck', 'Overturf',
- 'Oxhandler', 'Pealike', 'Pennywhistle', 'Peterson', 'Pieplow',
- 'Pinkerton', 'Porkins', 'Putney', 'Quakenbush', 'Rainwater',
- 'Rosenthal', 'Rubbins', 'Sackrider', 'Snuggleshine', 'Splern',
- 'Stevens', 'Stroganoff', 'Sugar-Gold', 'Swackhamer', 'Tippins',
- 'Turnipseed', 'Vinaigrette', 'Walkingstick', 'Wallbanger', 'Weewax',
- 'Weiners', 'Whipkey', 'Wigglesworth', 'Wimplesnatch', 'Winterkorn',
- 'Woolysocks')
-
- # 然后,程序开始执行 while 循环。
- # 将 while 循环语句的循环条件设置为 True,即让程序“一直运行,直到我让你停下来为止”。
- # 最终,你会使用 break 语句来结束这个循环。
- while True:
-
- # 在 while 循环体内,先从 first 元组中随机选择一个名字,并把这个名字赋给 firstName 变量。
- # random 模块的 choice()函数会随机地从非空序列中选择一个元素,并把该元素当作函数的返回值。
- # 就本例而言,非空序列指的是名字元组。
- firstName = random.choice(first)
-
- # 紧接着,从 last 元组中随机选择一个姓氏,并将它分配给 lastName 变量 。
-
- lastName = random.choice(last)
-
-
- # 为了使这个有趣的姓名在 IDLE 窗口中更加显眼,本示例程序会使输出结果之间包含一些空白行。
- print("\n\n")
- # 此时,你已经得到一组名字和姓氏,把它们输出在 shell 窗口中。
- # 向 print()函数提供可选参数 file=sys.stderr,使 IDLE 窗口中的“错误”信息显示为红色。
- # 同时,使用新的字符串格式化方法把姓名变量值转换为字符串。
- # 需要注意的是,旧的字符串格式方法指的是使用操作符 % 将变量值转换为字符串。
- # 在 Python 的官方网站可以获得更多与新的字符串格式化方法有关的信息。
- print("{} {}".format(firstName, lastName), file=sys.stderr)
- print("\n\n")
-
- # 之后,程序生成的假名就会显示出来。
- # 接着,利用 input 语句显示一段提示信息,询问用户是再来一次,还是退出程序。
- # 对于这个请求,如果用户直接按 Enter 键,那么变量 try_again 就不会捕捉到任何输入内容。
- # 由于没有返回任何内容,因此 if 条件语句不成立,while 循环会继续运行,程序会再次输出新的名字和姓氏对。
- try_again = input("\n\nTry again? (Press Enter else n to quit)\n")
-
- # 如果用户按 N 键,那么 if 语句中的条件成立,break 语句会被执行。
- # 此时,while 循环语句的判断条件也不再是 True,循环随之结束。
- # 为了避免用户不小心按下 Caps lock 键,利用字符串对象的 lower()函数把用户的输入值转换为小写字符。
- # 换言之,用户不必考虑输入的是小写字符还是大写字符,程序会总是将它视为小写。
- if try_again.lower() == "n":
- break
-
- # 最后,程序显示一条提示信息,告诉用户按 Enter 键结束程序。
- # 当用户按 Enter 键时,input()函数不会把返回值赋给任何变量,程序结束,同时控制台窗口关闭。
- input("\nPress Enter to exit.")
根据 Python 之禅(The Zen of Python)的说法,“做好一件事情的方法应该有且只有一种”。在实践的基础上,Python 社区不断推出新的编程指导原则,发布新的 Python 增强提议(Python Enhancement Proposals,PEP),这些提议涉及一系列编程规范。Python 发行版中的标准库也遵循相关编程规范。PEP 8 是这些增强提议中最重要的编程规范。新的编程规范不断涌现,而旧的编程规范随着语言的改变逐渐被淘汰,PEP 8 标准也随着时间的推移而不断演化。
PEP 8 标准不仅约定了 Python 中标识符的命名原则,还规定了空白行、制表符和空格的使用方式,以及每行允许的最大字符长度和可采用的注释方式。PEP 8 标准能提高代码的可读性,使所有的 Python 程序在编程规范上保持一致。当开始用 Python 编写程序时,你应该努力学习这些公认的编程规范,并养成遵守这些规范的良好习惯。
在跨职能团队中,标准化的名称和过程对程序的开发尤为重要。否则,科学家和工程师之间可能会产生很多误解。1999 年,由于不同的团队使用不同的测量单位,工程师们失去了对火星气候轨道飞行器(Mars Climate Orbiter)的控制。在过去的 20 年时间里,我建立了能够应用于工程上的地球计算机模型。工程师可以使用脚本将这些模型加载到专门的软件中。为了帮助那些没有经验的人提高效率,工程师们会在项目开发过程中共享这些脚本。由于这些“命令文件”是根据每个项目定制的,因此在模型更新期间,工程师会对属性名的更改感到恼火。实际上,他们的内部准则之一便是“要求建模者使用一致的属性名”。
尽管你已经熟悉 PEP 8 标准,但是仍然可能会犯错误。查看代码是否遵守 PEP 8 标准也是一件很麻烦的事情。幸运的是,你有许多工具可用,例如 Pylint、pycodestyle 和 Flake8。在这些工具的帮助下,你可以轻松地编写出遵循 PEP 8 标准的代码。对于本章中的这个项目,你可以使用 Pylint 模块检查其代码的规范性。
- pip install -U pylint==2.4
- pylint xx.py
每行展示一个消息,开头是 py 文件,后面的数字表示第几行几列,大写字母和数字代表消息码。例如,“pseudonyms.py:23:19: C0326”指的是 pseudonyms.py 中第 23 行第 19 列违背 Python 编程规范。下面是一些常见的 Pylint 模块消息码类型:
R:违反“良好实践”原则。
C:违反编程规范。
W:不严重的编程问题。
E:严重的编程问题(可能是一个错误)。
F:致命错误,阻止 Pylint 模块进一步运行。
消息码类型后的数字表示具体的错误,明细可以在 pylint 官网中查询到。
通过与 PEP 8 标准进行一致性对比,Pylint 模块会为你的代码打分。在这个例子中,代码的得分为 4(满分 10 分)。
当使用 Pylint 模块检查较短的脚本时,我更倾向于使用 Pylint 模块的默认设置,并忽略掉“无效常量名称”提示,我还喜欢使用 -rn
(-reports=n
的简写)选项,它会阻止 Pylint 模块返回大量无关的统计信息,经常遇到的另外一个问题是:它的默认最大行长为 100 个字符,但是,PEP 8 建议的最多字符个数为 79,你不必每次都手动输入这些选项和参数。你可以使用该模块的 --generate-rcfile
命令生成自定义的配置文件。例如,为了避免输出大量的统计信息,将最大行长设置为 79 个字符,在命令行窗口中输入如下内容,即可生成所需的配置文件:
pylint -rn --max-line-length=79 --generate-rcfile > myconfig.pylintrc
将自定义参数设置放在 --generate-rcfile > myconfig.pylintrc
命令之前,在扩展名 .pylintrc 的前面输入配置文件名。你可以像前面所做的那样,生成一个独立的配置文件来评估 Python 程序的规范性。当生成配置文件时,Pylint 模块允许设置配置文件的存储路径,但是默认情况下,它会自动保存在当前的工作目录中。
为了使用自定义的配置文件,你需要在待检查程序前输入自定义配置文件名,并在配置文件名前输入 --rcfile
。例如,若要在文件 myconfig.pylintrc 指定的配置下运行 pseudonyms_main.py 程序,则需要在命令行窗口中输入如下内容:
pylint --rcfile myconfig.pylintrc pseudonyms_main
修改后的代码:
- """从两个独立的名字元组中随机选择元素来生成有趣的假名"""
- import random
- import sys
-
-
- def main():
- """从两个名字元组中随机选择一些名字并输出到屏幕上"""
- print("Welcome to the Psych 'Sidekick Name Picker.'\n")
- print("A name just like Sean would pick for Gus:\n\n")
-
- first = ('Baby Oil', 'Bad News', 'Big Burps', "Bill 'Beenie-Weenie'",
- "Bob 'Stinkbug'", 'Bowel Noises', 'Boxelder', "Bud 'Lite'",
- 'Butterbean', 'Buttermilk', 'Buttocks', 'Chad', 'Chesterfield',
- 'Chewy', 'Chigger', 'Cinnabuns', 'Cleet', 'Cornbread',
- 'Crab Meat', 'Crapps', 'Dark Skies', 'Dennis Clawhammer',
- 'Dicman', 'Elphonso', 'Fancypants', 'Figgs', 'Foncy', 'Gootsy',
- 'Greasy Jim', 'Huckleberry', 'Huggy', 'Ignatious', 'Jimbo',
- "Joe 'Pottin Soil'", 'Johnny', 'Lemongrass', 'Lil Debil',
- 'Longbranch', '"Lunch Money"', 'Mergatroid', '"Mr Peabody"',
- 'Oil-Can', 'Oinks', 'Old Scratch', 'Ovaltine', 'Pennywhistle',
- 'Pitchfork Ben', 'Potato Bug', 'Pushmeet', 'Rock Candy',
- 'Schlomo', 'Scratchensniff', 'Scut', "Sid 'The Squirts'",
- 'Skidmark', 'Slaps', 'Snakes', 'Snoobs', 'Snorki', 'Soupcan Sam',
- 'Spitzitout', 'Squids', 'Stinky', 'Storyboard', 'Sweet Tea',
- 'TeeTee', 'Wheezy Joe', "Winston 'Jazz Hands'", 'Worms')
-
- last = ('Appleyard', 'Bigmeat', 'Bloominshine', 'Boogerbottom',
- 'Breedslovetrout', 'Butterbaugh', 'Clovenhoof', 'Clutterbuck',
- 'Cocktoasten', 'Endicott', 'Fewhairs', 'Gooberdapple',
- 'Goodensmith', 'Goodpasture', 'Guster', 'Henderson', 'Hooperbag',
- 'Hoosenater', 'Hootkins', 'Jefferson', 'Jenkins',
- 'Jingley-Schmidt', 'Johnson', 'Kingfish', 'Listenbee', "M'Bembo",
- 'McFadden', 'Moonshine', 'Nettles', 'Noseworthy', 'Olivetti',
- 'Outerbridge', 'Overpeck', 'Overturf', 'Oxhandler', 'Pealike',
- 'Pennywhistle', 'Peterson', 'Pieplow', 'Pinkerton', 'Porkins',
- 'Putney', 'Quakenbush', 'Rainwater', 'Rosenthal', 'Rubbins',
- 'Sackrider', 'Snuggleshine', 'Splern', 'Stevens', 'Stroganoff',
- 'Sugar-Gold', 'Swackhamer', 'Tippins', 'Turnipseed',
- 'Vinaigrette', 'Walkingstick', 'Wallbanger', 'Weewax', 'Weiners',
- 'Whipkey', 'Wigglesworth', 'Wimplesnatch', 'Winterkorn',
- 'Woolysocks')
-
- while True:
- first_name = random.choice(first)
- last_name = random.choice(last)
-
- print("\n\n")
- # 使用“致命错误”字体设置,在IDLE窗口中将输出的姓名颜色设为红色
- print("{} {}".format(first_name, last_name), file=sys.stderr)
- print("\n\n")
-
- try_again = input("\n\nTry again? (Press Enter else n to quit)\n ")
-
- if try_again.lower() == "n":
- break
-
- input("\nPress Enter to exit.")
-
- if __name__ == "__main__":
- main()
PEP 257 标准指出:文档字符串是一种常放在模块、函数、类和方法定义开头的字符串。文档字符串的作用是简要地描述代码的功能,它可能会包括诸如输入要求等在内的说明信息。下面是一个在函数中使用单行文档字符串的示例,该字符串在一对三引号内:
- def circ(r):
- """Return the circumference of a circle with radius of r.
- Arguments:
- r – radius of circle
- Returns:
- float: circumference of circle
- """
-
- c = 2 * r * math.pi
- return c
文档字符串的书写方式会因个人、项目和公司的不同而有所差别。因此,你会发现存在很多相互矛盾的规范。谷歌公司就有独具该公司特色的编程规范。科学界的某些人士喜欢使用 NumPy 文档字符串书写标准。reStructuresText 是一种流行的文档字符串格式化工具,它主要与工具 Sphinx 结合使用。通过 Python 代码的文档字符串,这些工具可为项目生成 HTML 和 PDF 格式的文档。如果阅读过一些 Python 模块的字符串文档,你可能看到过 Sphinx 字符串文档格式工具的使用。利用一款名为 pydocstring 的免费工具可以检查文档字符串是否符合 PEP 257 标准。
pip install pydocstyle
为了使用 pydocstring 工具,先打开命令行窗口,将当前工作目录切换至待检查代码所在的目录。如果在命令行窗口中未指定文件名,pydocstring 工具会检查该目录中的所有 Python 程序,并为每个程序生成相应的检查报告。如果程序的文档字符串满足 Python 编程规范,那么 pydocstring 工具不会返回任何内容。
这个结果说明代码的注释应该加标点。
Pylint 模块和 pydocstyle 工具都易于安装和运行,它们可以帮助你更好地学习并遵守 Python 社区公认的编程标准。当你通过 Web 论坛寻求帮助,并希望获得友好、温和的回答时,你应该遵循的一个原则就是:在代码发布到论坛之前,先运行 Pylint 模块,评估代码的规范性。