本节书摘来自异步社区《Python 3程序开发指南(第2版•修订版)》一书中的第1章,第1.3节,作者[英]Mark Summerfield,王弘博,孙传庆 译,更多章节内容可以访问云栖社区“异步社区”公众号查看。
1.3 实例
在前面的几节中,我们介绍了足以编写实际程序的Python知识与技术。在这一节中,我们将介绍两个完整的程序,这些程序只涉及前面已经讲过的知识。一方面是为了展示前面所学的知识可以完成什么任务,一方面也是为了巩固前面所学的知识。
在后面的章节中,我们会逐渐学习更多的Python知识与库模块,以便于编写出比这里展示的程序更精确与更强壮的程序——但是首先我们必须先掌握这些基础知识。
1.3.1 bigdigits.py
这里给出的第一个程序非常短小,尽管该程序也有一些微妙之处,包括列表组成的列表等。这个程序的功能是:在命令行中提供一个数值,之后该程序会使用“大”数字向控制台输出该数值。
在大量用户共享高速行式打印机的站点上,使用这种技术是很常见的做法,即为每个用户的打印作业打印一个引导页,使其包含用户名与其他有助于区分不同用户的详细资料。
我们分3个部分查看该程序的代码:import部分;创建列表(其中存放程序要使用的数据)部分;处理部分本身。不过,我们首先看一下运行的效果:
bigdigits.py 41072819
* * *** ***** *** *** * ****
** ** * * * * * * * ** * *
* * * * * * * * * * * * *
* * * * * * * *** * ****
****** * * * * * * * * *
* * * * * * * * * *
* *** *** * ***** *** *** *
我们没有展示控制台提示符(或UNIX用户的./),而是将这些内容默认为已经存在的。
import sys
由于我们必须从命令行中读入一个参数(也就是要输出的数值),我们需要访问sys.argv列表,因此我们从导入sys模块开始。
我们以字符串列表的形式展示每个数值,比如,下面展示的是zreo:
Zero = [" *** ",
" * * ",
"* *",
"* *",
"* *",
" * * ",
" *** "]
这里需要注意的一个细节是,Zero的字符串列表表示形式跨越了多个行。通常,Python语句只占用一行,但是也可以跨越多行,比如使用圆括号包含的表达式、列表、集合、字典字面值、函数调用参数列表以及多行语句(除最后一行之外,每行的行终结符都使用反斜线进行引导并转义处理)。上面的这些Python语句可以跨越任意多的行,代码缩排也并不会影响第二行以及后续的行。
用于表示数值的每个列表包含7个字符串,在对同一个数值的表示中,这些字符串是等宽的,而表示不同数值的字符串宽度不同。表示其他数值的列表在形式上与上面给出的Zero类似。下面给出的几个表示主要是出于紧致性考虑,因而不那么形象和清晰:
One = [" * ", "** ", " * ", " * ", " * ", " * ", "***"]
Two = [" *** ", "* *", "* * ", " * ", " * ", "* ", "*****"]
# ...
Nine = [" ****", "* *", "* *", " ****", " *", " *", " *"]
我们还需要的最后一个数据结构是所有数字列表组成的列表:
Digits = [Zero, One, Two, Three, Four, Five, Six, Seven, Eight, Nine]
我们也可以直接创建Digits列表,而不必创建额外的变量,例如:
Digits = [
[" *** ", " * * ", "* *", "* *", "* *",
" * * ", " *** "], # Zero
[" * ", "** ", " * ", " * ", " * ", " * ", "***"], # One
# ...
[" ****", "* *", "* *", " ****", " *", " *",
" *"] # Nine
]
我们更愿意使用单独的变量来分别表示每个数值,一方面是为了便于理解,另一方面是因为使用变量来表示看起来更整洁。
下面一起展示了余下的代码部分,以便你在阅读其后的解释之前自己可以设想其工作方式。
try:
digits = sys.argv[1]
row = 0
while row < 7:
line = ""
column = 0
while column < len(digits):
number = int(digits[column])
digit = Digits[number]
line += digit[row] + " "
column += 1
print(line)
row += 1
except IndexError:
print("usage: bigdigits.py <number>")
except ValueError as err:
print(err, "in", digits)
上面这段代码整体包含在一个异常处理模块中,并可以捕获两个异常。该段代码首先取回程序的命令行参数,与所有Python列表类似,sys.argv列表的索引项是从0开始的,索引位置为0的数据项是调用的程序名,因此,在一个运行的程序中,该列表总是至少包含一项。如果没有给定参数,我们会在一个单数据项的列表中尝试访问第二个数据项,并导致产生一个IndexError异常。如果发生这种情况,控制流立即转向相应的异常处理块,这里只是简单地打印出程序的用法。在Try块结束后,程序继续执行,但是由于已经没有更多的代码,因此程序只是简单地退出。
如果没有发生IndexError异常,那么digits字符串会存放命令行参数,如果一切正常,就应该是一个数字字符序列。(记住,要素2中讲过,标识符对大小写敏感,因此,digits与Digits是不同的。)每个大数都使用7个字符串表示,为正确地输出数值,我们必须首先输出每个数字的顶行,之后是下一行,依此类推,直至输出所有的7行。我们使用一个while循环,以便逐行迭代。我们也可以采用另一种方法:for row in (0, 1, 2, 3, 4, 5, 6):,后面我们还将看到一种更好的、使用内置的range()函数的方法。
我们使用line字符串来存放所有数字的行字符串,之后根据列进行循环,也就是说,根据命令行参数中每个相继的字符进行循环。我们使用digits[column]取回每个字符,并将数字转换为称为number的整数。如果转换失败,就会产生一个ValueError异常,控制流立即转向相应的异常处理模块。这种情况下,我们将打印出错误消息,并在try块之后恢复控制。与前面类似,由于没有其他代码等待执行,因此程序只是简单地退出。
如果转换成功,我们就使用number作为索引来存取digits列表,并从其中抽取字符串列表digit,之后我们从这一列表中将相应的字符串添加到我们正在构建的行,并添加两个空格,以便在数字之间添加水平分隔。
内部的while循环每次结束时,我们会打印出刚构建好的行。理解这一程序的关键之处在于我们将每个数字的row字符串添加到当前row的行。读者可以尝试运行该程序,以便理解其运作方式。在章后练习中,我们将再次讲到该程序,以便对其输出进行稍许改变。
1.3.2 generate_grid.py
我们频繁面临的需求是测试数据的生成。由于不同场景下测试数据变化巨大,因此无法找到一个满足所有测试数据需求的通用程序。由于编写与修改Python程序都很容易, Python经常被用于生成测试数据。在这一小节中,我们将创建一个生成随机整数组成的网格的程序,用户可以规定需要多少行、多少列,以及整数所在的区域。我们首先从一个运行实例开始:
generate_grid.py
rows: 4x
invalid literal for int() with base 10: '4x'
rows: 4
columns: 7
minimum (or Enter for 0): -100
maximum (or Enter for 1000):
554 720 550 217 810 649 912
-24 908 742 -65 -74 724 825
711 968 824 505 741 55 723
180 -60 794 173 487 4 -35
该程序以交互式的方式运行,最开始在输入行数时,由于输入的行数有误,导致程序打印一条错误消息,并要求用户重新输入行数。对于maximum,我们只是简单地按Enter键,以便接受默认值。
我们将分别解读该程序的4个部分:import、函数get_int()的定义(此函数比要素8中展示的类似函数更复杂)、用户交互以便获取要使用的值、处理过程本身。
import random
我们需要random模块,以便访问其中的random.randinit()函数。
def get_int(msg, minimum, default):
while True:
try:
line = input(msg)
if not line and default is not None:
return default
i = int(line)
if i < minimum:
print("must be >=", minimum)
else:
return i
except ValueError as err:
print(err)
这一函数需要3个参数:一个消息字符串、一个最小值、一个默认值。如果用户只是简单地按Enter键,就有两种可能性。如果default为None,也就是说没有给定默认值,那么控制流将转到int()行,在该处转换将失败(因为无法转换为整数),并产生一个ValueError异常;如果default非None,就返回该值。否则,函数将尝试把用户输入的文本转换为整数,如果转换成功,接下来将检查该整数是否至少等于指定的minimum。
因此,该函数的返回总是两种情况,或者是default(用户只是按Enter键),或者是一个有效的整数(大于或等于指定的minimum)。
rows = get_int("rows: ", 1, None)
columns = get_int("columns: ", 1, None)
minimum = get_int("minimum (or Enter for 0): ", -1000000, 0)
default = 1000
if default < minimum:
default = 2 * minimum
maximum = get_int("maximum (or Enter for " + str(default) + "): ",
minimum, default)
通过我们的get_int()函数,可以很容易地获取行数、列数以及用户需要的最小随机数值。对于给定默认值None的行数与列数,也就是没有指定默认值的情况,用户必须输入一个整数。对于minimum,我们提供的默认值为0:对于maximum,我们提供的默认值为1000或minimum的2倍(如果minimum大于或等于1000)。
与前面的例子类似,函数调用参数列表可以跨越任意数量的行数,并且缩排与第二行及后继行无关。
在确定用户需要的具体行数、列数以及随机数的最大值与最小值后,就可以进行具体的随机数生成过程:
row = 0
while row < rows:
line = ""
column = 0
while column < columns:
i = random.randint(minimum, maximum)
s = str(i)
while len(s) < 10:
s = " " + s
line += s
column += 1
print(line)
row += 1
为生成随机数网格,我们使用3个while循环,外部循环以行数进行循环,中间循环以列数进行循环,内部循环则以字符进行循环。在中间循环中,我们获取指定范围内的随机数,并将其转换为字符串。内部while循环用于对字符串进行填充(填充数据为空格),以便每个数字都使用10个字符的字符串表示,对每一行,使用字符串line来累积数值,在每一列的数字添加完毕后,就打印出该行表示的数字,至此,第二个程序功能讲解完毕。
Python提供了非常高级的格式化功能,以及对for ... in循环的良好支持能力,因此,bigdigits.py与generate_grid.py这两个程序的更真实的版本会使用for ... in循环,generate_grid.py程序将使用Python的字符串格式化功能,而不是像这里这样不带修饰地使用空格进行填充。但是在本章中,我们将自己约束在使用本章介绍的8个关键要素进行程序设计,并且这8个要素对编写完整而有用的程序也已足够。在接下来的每一章中,我们都将学习Python的一些新特性,因此,随着本书内容的推进,所看到的程序将逐步复杂起来。