《Python基础教程》笔记 第19章 有趣的编程
在接下来的10章中,你将把新学到的技能付诸应用。每章都包含一个DIY项目,既介绍了实现解决方案所需的工具,同时又提供了很大的实验空间。
本章将介绍一些通用的Python编程指南。
19.1 为何要有趣
Python的优点之一是让编程变得有趣。当你感到有趣时,就更容易变得高效;而Python有趣的地方之一就是让你非常高效。这就形成了生活中很难得的良性循环。
19.2 编程柔术
柔术(jujitsu)是一种日本武术,类似于从它衍生而来的柔道(judo)和合气道(aikido),都注重灵活的反应,宁弯勿折:不力图用计划好的动作打击对手,而是顺势而为,借力打力。这样(理论上)能打败比你更高大、更狡猾、更强壮的对手。
如何将这种理念用于编程呢?关键在“柔”字上,也就是灵活性。在编程中遇到麻烦时(肯定会遇到),不要固守最初的设计和想法,而要灵活变通,以柔克刚(roll with the punches)。要做好应对并适应变化的准备,不要将未预料到的事件视为令人沮丧的打击,而是将其看作创造性探索新的选择和可能性的振奋人心的起点。
这种灵活性涵盖很多方面,这里只简要地介绍其中的两个:
- 原型设计(prototyping):Python的优点之一是让你能够快速地编写程序。编写原型程序是更深入地了解面临的问题的一种很好的方法。
- 配置(configuration):配置旨在让程序的某些部分修改起来更容易——对于你和你的用户来说都如此。
第三个方面是自动化测试,要能够轻松地修改程序,这绝对必不可少。有了测试后,就能确信程序在修改后仍然能正确地运行。原型设计和配置将在接下来的两节讨论。有关测试的详细信息参见第16章。
19.3 原型设计
Python(交互式解释器)让你能够轻松地创建程序的原型,以便了解其工作原理。
注意:在这里,原型(prototype)意味着尝试性的实现,即一个实现了最终程序的主要功能的模型(mock-up),后期可能需要重写,也可能不用。通常,最初的原型都能变成可以工作的程序。
对程序的结构(例如需要哪些类和函数)有一定的想法后,建议你实现一个功能可能极其有限的简单版本。当你有了可运行的程序后,将会发现接下来的工作容易得多。你可以添加新功能,修改不喜欢的地方,等等。你能够真正明白程序如何工作,而不仅仅是设想或画草图。
使用任何编程语言都可以进行原型设计,但Python的优点在于编写原型的投入很少。如果发现设计不够精巧,只需将原型丢弃从头开始。
在本书后面的项目中,将始终使用原型设计,而不是预先进行详细的分析和设计。每个项目都有两个实现。第一个实现是摸着石头过河:拼凑出一个能够解决问题(或部分问题)的程序,以便了解需要的组件以及对优秀解决方案的要求。最好的教训可能就是看到程序的各种缺陷。基于这些新的认识,采取另一种更明智的做法。当然,你可以随意修改代码,甚至开始第三次实现。通常,推倒重来所需的时间没有你想像中那么多。如果你已经充分考虑了程序的实际情况,敲代码应该不会花费太长时间。
注意:虽然这里提倡使用原型,但在任何时候务必对推倒重来持谨慎态度,尤其是在原型上投入了不少时间和精力时。对原型进行重构和修改,将其完善为功能性更好的系统可能是更好的选择。
19.4 配置
本节将回到抽象这一重要原则。第6、7章展示了如何通过将代码放在函数和方法中并将较大的结构隐藏在类中来抽象代码。下面来看看另一种更简单的在程序中引入抽象的方式:提取代码中的符号常量(symbolic constant)。
19.4.1 提取常量
所谓常量(constant),指的是内置的字面值,例如数字(42
)、字符串("hello"
)和列表([1, 2, 3]
)。可以将这些值存储在全局变量中,而不是在程序中反复输入。虽然前面(6.5节)警告过你慎用全局变量,但只要不修改它们,而是将它们当作常量(即“符号常量”)就不会有问题。要指出一个变量应被视为符号常量,可以使用一种特殊的命名约定:大写字母+下划线(例如SCREAMING_SNAKE_CASE
)。
注:这只是一种约定,Python并没有类似于C++的const
关键字可以保证变量不会被修改。
例如,在计算圆的面积和周长的程序中,可以在每次需要π值时都写一遍3.14。但更好的办法是在程序开头写PI = 3.14
,然后使用名字PI
而不是数字本身。这样,以后要使用更精确的值时,只需修改这一行代码即可。请记住:每当你需要多次写一个常量时,都应考虑将其放在全局变量中。
19.4.2 配置文件
为自己方便而提取常量是一回事,但有些常量可以暴露给用户。例如,GUI程序的背景色、街机游戏启动时显示的问候语、Web浏览器的默认起始页面。
可以将这些配置变量放在单独的文件中,而不是放在模块开头。为此,最简单的方法是为配置创建一个单独的模块。例如,如果PI
是在模块文件config.py中设置的,就可以在主程序中这样做:
1
from config import PI
警告:使用配置文件有利有弊。一方面,配置很有用;但另一方面,对整个项目使用集中的、共享的变量库会降低模块化程度,增大耦合程度。务必不要破坏抽象(例如封装)。
另一种方法是使用标准库模块configparser
,从而可以在配置文件中使用标准格式(类似于Windows INI文件格式)。
1
greeting: Hello, world!
必须使用标头(类似于[files]
)将配置文件分成几个小节(section)。名称可以随意指定,但必须将它们用方括号括起来。代码清单19-1是一个简单的配置文件,代码清单19-2是一个使用它的程序。有关configparser
模块的详细信息参见标准库文档。
建议你考虑让程序是可配置的,这样用户可以根据自己的偏好调整程序,使用时也会心情愉悦。
配置的级别
可配置性是UNIX编程传统不可或缺的一部分。《UNIX编程艺术》描述了配置信息的如下三个来源,应该按照这个顺序查询,后面的覆盖前面的:
- 配置文件:如本节描述。
- 环境变量:可使用
os.environ
获取。 - 命令行参数:可使用
sys.argv
或argparse
。
19.5 日志
日志(logging)与测试有一定的关系,在需要大规模改造程序时很有用,它无疑能够帮助你发现问题和bug。日志基本上就是收集与程序运行相关的数据,供你事后检查。
print
语句是一种非常简单的日志形式。只需在程序开头放置类似于下面的语句:
1
log = open('logfile.txt', 'w')
然后就可以将任何感兴趣的程序状态信息写入这个文件,例如:
1
2
3
print('Downloading file from URL', url, file=log)
text = urllib.urlopen(url).read()
print('File successfully downloaded', file=log)
如果程序在下载期间崩溃,这种方法就不管用了。在每次写入后刷新文件会更安全。
实际上,正确的做法是使用标准库的logging
模块。基本用法非常简单,如代码清单19-3所示。
运行这个程序,将生成下面的日志文件(mylog.log):
1
2
INFO:root:Starting program
INFO:root:Trying to divide 1 by 0
可以看到,试图将1除以0以后没有日志记录,因为这个错误导致程序终止。这个错误很简单,可以根据程序崩溃时打印的异常栈跟踪来确定问题出在什么地方。不会导致程序终止、而只是让它行为异常的bug是最难查找的,但通过查看详细的日志文件也许能够帮助你了解发生了什么。
通过适当地配置logging
模块,可以让日志以你希望的方式运行。下面是一些示例:
- 只记录不同级别的日志(调试、信息、警告、错误、致命等)。默认情况下,只记录警告及以上(这就是代码清单19-3中显式地将级别设置为
logging.INFO
的原因)。 - 只记录与程序特定部分相关的日志。
- 记录有关时间、日期等方面的信息。
- 记录到其他位置,例如套接字。
- 配置日志记录器,将一些或大部分日志过滤掉,这样无需重写程序就能获得所需的日志信息。
logging
模块非常复杂,文档中有许多要学习的内容。
预告
现在该真刀真枪地开始编程了,也是时候进入项目了。这10个项目章节的结构都类似,包括以下几节:
- 问题描述:概述项目的主要目标,包括一些背景信息。
- 有用的工具:描述对项目可能有用的模块、类、函数等。
- 准备工作:介绍开始编程前需要做的准备工作,可能包括安装必要的框架,以便对实现进行测试。
- 初次实现:这是第一次出击——旨在更深入地了解问题的尝试性实现。
- 再次实现:完成初次实现后,你可能对问题有更好的理解,让你能够创建新的改进版本。
- 进一步探索:最后,给出一些进一步实验和探索的建议。