- A+
学习目标
软件开发过程
运行已经编写的程序很容易。较难的部分实际上是先得到一个程序。计算机是非常实在的,必须告诉它们要做什么,直至最后的细节。编写大型程序是一项艰巨的挑战。如果没有系统的方法,几乎是不可能的。
创建程序的过程通常被分成几个阶段,依据是每个阶段中产生的信息。简而言之,你应该做以下工作。
分析问题 确定要解决的问题是什么。尝试尽可能多地了解它。除非真的知道问题是什么,否则就不能开始解决它。
确定规格说明 准确描述程序将做什么。此时,你不必担心程序“怎么做”,而是要确定它“做什么”。对于简单程序,这包括仔细描述程序的输入和输出是什么以及它们的相互关系。
创建设计 规划程序的总体结构。这是描述程序怎么做的地方。主要任务是设计算法来满足规格说明。
实现设计 将设计翻译成计算机语言并放入计算机。在本书中,我们将算法实现为Python程序。
测试/调试程序 试用你的程序,看看它是否按预期工作。如果有任何错误(通常称为“缺陷”),那么你应该回去修复它们。定位和修复错误的过程称为“调试”程序。在调试阶段,你的目标是找到错误,所以应该尝试你能想到的“打破”程序的一切可能。记住这句老格言:“没有什么能防住人犯傻,因为傻子太聪明了。”
维护程序 继续根据用户的需求开发该程序。大多数程序从来没有真正完成,它们在多年的使用中不断演进。
由于篇幅有限,今天分享之前先说下这个,,如果大家喜欢的话我会再更新,专注学习Python技术的小伙伴可以进群(588090942)一起交流学习,群里还有大量学习资料可供大家自行下载参看,欢迎大家一起来交流讨论
示例程序:温度转换器
让我们通过一个真实世界的简单例子,来体验软件开发过程的步骤,其中涉及一个虚构的计算机科学学生Susan Computewell。
Susan正在德国学习一年。她对语言没有任何问题,因为她能流利地使用许多语言(包括Python)。她的问题是,很难在早上弄清楚温度从而知道当天该穿什么衣服。Susan每天早上听天气报告,但温度以摄氏度给出,她习惯了华氏度。
幸运的是,Susan有办法解决这个问题。作为计算机科学专业的学生,她去任何地方总是带着她的笔记本计算机。她认为计算机程序可能会帮助她。
Susan开始分析她的问题。在这个例子中,问题很清楚:无线电广播员用摄氏度报气温,但Susan只能理解华氏温度。
接下来,Susan考虑可能帮助她的程序的规格说明。输入应该是什么?她决定程序将允许她输入摄氏温度。输出呢?程序将显示转换后的华氏温度。现在她需要指定输出与输入的确切关系。
苏珊快速估算了一下。她知道0摄氏度(冰点)等于32华氏度,100摄氏度(沸点)等于212华氏度。有了这个信息,她计算出华氏度与摄氏度的比率为(212−32)/(100−0) = (180/100) = 9/5。使用F表示华氏温度,C表示摄氏温度,转换公式的形式为F = (9/5)C + k,其中k为某个常数。代入0和32分别作为C和F,Susan立即得到k = 32。所以最后的关系公式是F = (9/5)C + 32。这作为规格说明似乎足够了。
请注意,这描述了能够解决这个问题的许多可能程序中的一个。如果Susan有人工智能(AI)领域的背景,她可能会考虑写一个程序,用语音识别算法实际收听收音机播音员,获得当前的温度。对于输出,她可以让计算机控制机器人进入她的衣柜,并根据转换后的温度选择适当的服装。这将是一个更有野心的项目,一点也不夸张!
当然,机器人程序也会解决问题分析中识别的问题。规格说明的目的,是准确地决定这个特定的程序要做什么,从而解决一个问题。Susan知道,最好是先弄清楚她希望构建什么,而不是一头钻进去开始编程。
Susan现在准备为她的问题设计一个算法。她马上意识到这是一个简单算法,遵循标准模式“输入、处理、输出”(IPO)。她的程序将提示用户输入一些信息(摄氏温度),处理它,产生华氏温度,然后在计算机屏幕上显示结果,作为输出。
Susan可以用一种计算机语言来写她的算法。然而,正式将它写出来需要相当的精度,这常常会扼杀开发算法的创造性过程。作为替代,她用“伪代码”编写算法。伪代码只是精确的英语,描述了程序做的事。这意味着既可以交流算法,又不必让大脑承担额外的开销,正确写出某种特定编程语言的细节。
下面是Susan的完整算法:
你可以看到,Susan用值0和100来测试她的程序。看起来不错,她对解决方案感到满意。她特别高兴的是,似乎没有必要调试(这很不寻常)。
程序要素
既然已经知道了编程过程,你就“几乎”准备好开始自己编写程序了。在此之前,你需要更完整的基础,了解Python的基本知识。接下来的几节将讨论一些技术细节,这对编写正确程序至关重要。这种材料看起来有点乏味,但你必须掌握这些基础,然后再进入更有趣的领域。
1 名称
你已经看到,名称是编程的重要组成部分。我们为模块命名(例如convert),也为模块中的函数命名(例如main)。变量用于为值命名(例如celsius和fahrenheit)。从技术上讲,所有这些名称都称为“标识符”。Python对标识符的构成有一些规则。每个标识符必须以字母或下划线(“_”字符)开头,后跟字母、数字或下划线的任意序列。这意味着单个标识符不能包含任何空格。
根据上述规则,以下都是Python中的合法名称:
标识符区分大小写,因此对Python来说,spam、Spam、sPam和SPAM是不同的名称。在大多数情况下,程序员可以自由选择符合这些规则的任何名称。好的程序员总是试图选择一些名字,它们能描述被命名的东西。
需要注意一件重要的事情:一些标识符是Python本身的一部分。这些名称称为“保留字”或“关键字”,不能用作普通标识符。Python关键字的完整列表如表1所列。
表1 Python关键字
Python还包括相当多的内置函数,例如我们用过的print函数。虽然在技术上可以将内置的函数名称标识符用于其他目的,但这通常是一个“非常糟糕”的主意。例如,如果你重新定义print的含义,那么就无法再打印信息。你也会让所有阅读程序的Python程序员感到非常困惑,他们预期print指的是内置函数。内置函数的完整列表可在附录A中找到。
2 表达式
程序操作数据。到目前为止,我们已经在示例程序中看到了数字和文本两种不同类型的数据。我们将在后面的节中详细讨论这些不同的数据类型。现在,你只需要记住,所有的数据必须以一些数字格式存储在计算机上,不同类型的数据以不同的方式存储。
产生或计算新数据值的程序代码片段称为“表达式”。最简单的表达式是字面量。字面量用于表示特定值。在chaos.py中,你可以找到数字3.9和1。convert.py程序包含9、5和32。这些都是数字字面量的例子,它们的含义显而易见:32就是代表32(数字32)。
我们的程序还以一些简单的方式处理文本数据。计算机科学家将文本数据称为“字符串”。你可以将字符串视为可打印字符的序列。Python中通过将字符括在引号("")中来表示字符串字面量。如果你回头看看我们的示例程序,可以发现一些字符串字面量,例如"Hello"和"Enter a number between 0 and 1:"。这些字面量产生的字符串包含引号内的字符。请注意,引号本身不是字符串的一部分。它们只是告诉Python创建一个字符串的机制。
将表达式转换为基础数据类型的过程称为“求值”。在Python shell中键入表达式时,shell会计算表达式并打印出结果的文本表示。请考虑以下简短的交互:
请注意,当shell显示字符串的值时,它将字符序列放在单引号中。这样让我们知道该值实际上是文本而不是数字(或其他数据类型)。在最后一次交互中,我们看到表达式"32"产生一个字符串,而不是一个数字。在这种情况下,Python实际上是存储字符“3”和“2”,而不是数字32的表示。如果你现在不太明白,不要太担心。我们在后面的节中讨论这些数据类型时,你的理解就会变得更加清晰。
一个简单的标识符也可以是一个表达式。我们使用标识符作为变量来给名字赋值。当标识符作为表达式出现时,它的值会被取出,作为表达式的结果。下面是与Python解释器的交互,展示了变量作为表达式:
首先,变量x被赋值为5(使用数字字面量5)。在第二行交互中,我们要求Python对表达式x求值。作为响应,Python shell打印出5,这是刚才赋给x的值。当然,如果我们明确要求Python用print语句打印x,也会得到相同的结果。最后一个交互展示了如果尝试使用未赋值的变量,会发生什么。Python找不到值,所以它报告NameError。这说明没有该名称的值。这里的要点是,变量总是必须赋一个值,然后才能在表达式中使用。
较复杂、较有趣的表达式可以通过组合较简单的表达式和操作符来构造。对于数字,Python提供了一组标准的数学运算:加法、减法、乘法、除法和乘方。相应的Python运算符为“+”“-”“*”“/”和“**”。下面是一些来自chaos.py和convert.py的复杂表达式的例子:
空格在表达式中没有作用。最后一个表达式如果写成9/5*celsius+32,结果完全相同。通常,在表达式中加一些空格让它更容易阅读,是个好方法。
Python的数学运算符遵循的优先级和结合律,与你在数学课上学到的相同,包括使用括号来改变求值的顺序。在自己的程序中构建复杂表达式应该没什么困难。请记住,只有圆括号在数字表达式中是允许的。如果需要,可以嵌套使用它们,创建如下的表达式:
顺便说一句,Python还提供了字符串的运算符。例如,可以“加”字符串。
这被称为“连接”。如你所见,效果是创建一个新的字符串,把两个字符串“粘”在一起。
3 输出语句
既然有了基本的构建块(标识符和表达式),你就可以更完整地描述各种Python语句。 你已经知道信息可以使用Python的内置函数print在屏幕上显示。到目前为止,我们已经看了几个例子,但我还没有详细解释打印功能。像所有的编程语言一样,Python对每个语句的语法(形式)和语义(意义)有一套精确的规则。计算机科学家已经开发了复杂的符号表示法,称为“元语言”,用于描述编程语言。在本书中,我们将依靠一个简单的模板符号表示法来说明各种语句的语法。
因为print是一个内置函数,所以print语句与任何其他函数调用具有相同的一般形式。我们键入函数名print,后面带上括号中列出的参数。下面是用我们的模板符号时print语句看起来的样子:
这两个模板展示了两种形式的print语句。第一个表示print语句可以包含函数名print,后面带上带括号的表达式序列,用逗号分隔。模板中的尖括号符号(<>)用于表示由Python代码的其他片段填充的“槽”。括号内的名称表示缺少什么,expr表示一个表达式。省略号(“...”)表示不确定的序列(在这个例子中是表达式)。你实际上不会输入圆点。第二个版本的print语句表明,不打印任何表达式的print也是合法的。
就语义而言,print语句以文本形式显示信息。所有提供的表达式都从左到右求值,结果值以从左到右的方式显示在输出行上。默认情况下,在显示的值之间放置一个空格字符。作为示例,下面print语句的序列:
产生的输出为:
最后一个语句说明了,字符串字面量表达式如何经常在print语句使用,作为标记输出的方便方法。
注意,连续的print语句通常显示在屏幕的不同行上。空print(无参数)生成空行输出。在背后,真正发生的是,在打印所有提供的表达式之后,print函数自动附加某种结束文本。默认情况下,结束文本是表示行结束的特殊标记字符(表示为“
”)。我们可以通过包含一个附加参数显式地覆盖这个默认值,从而改变这种行为。这里使用命名参数的特殊语法,或称为“关键字”参数。
包含指定结束文本的关键字参数的print语句的模板如下:
命名参数的关键字是end,它使用“=”符号赋值,类似于变量赋值。注意,在模板中我已经显示其默认值,即行末字符。这是一种标准方式,用于显示在未明确指定某个其他值时,关键字参数具有的值。
print语句中的end参数有一个常见用法,即允许多个print构建单行输出。例如:
注意,第一个print语句的输出如何以空格(" ")而不是行末字符结束,第二个语句的输出紧跟在空格之后。
4 赋值语句
Python中最重要的语句之一是赋值语句。我们在前面的例子中已经看到了一些。
简单赋值
基本赋值语句具有以下形式:
这里variable是一个标识符,expr是一个表达式。赋值的语义是,右侧的表达式被求值,然后产生的值与左侧命名的变量相关联。
下面是我们已经看到的一些赋值:
最后一个赋值语句展示了如何使用变量的当前值来更新它的值。在这个例子中,我只是对以前的值加1。记住,变量的值可以改变,这就是为什么它们被称为变量的原因。
有时,将变量看作计算机内存中的一种命名的存储位置是有帮助的,我们可以在其中放入一个值。当变量更改时,旧值将被删除,并写入一个新值。图1展示了用这个模型来描绘x = x + 1的效果。这正是赋值在某些计算机语言中工作的方式。这也是查看赋值效果的一种非常简单的方式,你会在整本书中看到类似这样的图片。
图1 x = x + 1的视图,变量就像盒子
Python赋值语句实际上与“变量盒子”模型略有不同。在Python中,值可能最终放在内存中的任何位置,而变量用于引用它们。对变量赋值就像把一个黄色小粘贴便签放在值上,并说“这是x”。图2给出了一个更准确的Python赋值的效果。箭头用于显示变量引用的值。请注意,旧值不会被新值擦除,变量只需切换到引用新值。效果就像将粘贴便签从一个对象移动到另一个对象一样。这是赋值在Python中实际工作的方式,所以你会看到这样一些粘贴便签样式的图片散布在本书中。
图2 x = x + 1的(Python)视图,变量就像便签
顺便说一句,即使赋值语句不直接导致变量的旧值被擦除和覆盖,你也不必担心计算机内存中充满“被丢弃”的值。如果一个值不再被任何变量引用,它就不再有用。Python将自动从内存中清除这些值,以便空间可以用于存放新值。这就像检查你的衣柜,抛出没有粘贴便签标记的东西。实际上,这个自动内存管理的过程确实被称为“垃圾收集”。
赋值输入
输入语句的目的是从程序的用户那里获取一些信息,并存储到变量中。一些编程语言有一个特殊的语句来做到这一点。在Python中,输入是用一个赋值语句结合一个内置函数input实现的。输入语句的确切形式,取决于你希望从用户那里获取的数据类型。对于文本输入,语句如下所示:
重要的是要记住,如果希望得到一个数字,而不是一些原始文本(字符串),需要对input进行eval。
如果你仔细阅读示例程序,可能会注意到所有这些提示结尾处的引号内的空格。我通常在提示的末尾放置一个空格,以便用户输入的内容不会紧接着提示开始。放上空格可以让交互更容易阅读和理解。
虽然我们的数字示例特别提示用户输入数字,但在这个例子中,用户键入的只是一个数字字面量,即一个简单的Python表达式。事实上,任何有效的表达式都是可接受的。请考虑下面与Python解释器的交互:
这里,提示输入表达式时,用户键入“3 + 4 * 5”。Python对此表达式求值(通过eval),并将值赋给变量ans。打印时,我们看到ans的值为23,与预期一样。在某种意义上,input-eval组合就像一个延迟的表达式。示例交互产生完全相同的结果,就像我们简单地写成ans = 3 + 4 * 5一样。不同的是,表达式由用户在语句执行时提供,而不是由程序员在编程时输入。
注意:eval函数功能非常强大,也有“潜在的危险”。如本例所示,当我们对用户输入求值时,本质上是允许用户输入一部分程序。Python将尽职尽责地对他们输入的任何内容求值。了解Python的人可以利用这种能力输入恶意指令。例如,用户可以键入记录计算机上的私人信息或删除文件的表达式。在计算机安全中,这被称为“代码注入”攻击,因为攻击者将恶意代码注入正在运行的程序中。
作为一名新程序员,编程给自己个人使用,计算机安全不是很大的问题。如果你坐在一台运行Python程序的计算机前面,你可能拥有对系统的完全访问权限,并且可以找到更简单的方法来删除所有文件。然而,如果一个程序的输入来自不受信任的来源,例如来自互联网上的用户,使用eval可能是灾难性的。
同时赋值
有一个赋值语句的替代形式,允许我们同时计算几个值。它看起来像这样:
让我们来看看这个序列是如何工作的。
我们已经为每个变量获得了一个值。这个例子只用了两个值,但可以扩展到任意数量的输入。
当然,我们也可以通过单独的input语句获得用户的输入:
某种程度上,这可能更好,因为单独的提示对用户来说信息更准确。在这个例子中,决定采用哪种方法在很大程度上是品位问题。有时在单个input中获取多个值提供了更直观的用户接口,因此在你的工具包中,这是一项好技术。但要记住,多个值的技巧不适用于字符串(非求值)输入,如果用户键入逗号,它只是输入字符串中的一个字符。逗号仅在随后对字符串求值时,才成为分隔符。
确定循环
你已经知道,程序员用循环连续多次执行一系列语句。最简单的循环称为“确定循环”。这是会执行一定次数的循环。也就是说,在程序中循环开始时,Python就知道循环(或“迭代”)的次数。例如,前面介绍的chaos程序用了一个总是执行10次的循环:
你能看到这两个例子做了什么吗?依次使用列表中的每个值执行了循环体。列表的长度决定了循环执行的次数。在第一个例子中,列表包含4个值,即0至3,并且简单地打印了这些连续的i值。在第二个例子中,odd取前5个奇数的值,循环体打印了这些数字的平方。
现在,让我们回到这一节开始的例子(来自chaos.py)再看一下循环头:
表达式的值确定了循环执行的次数。索引变量的名称实际上并不重要,程序员经常使用i或j作为计数循环的循环索引变量。只要确保使用的标识符没有用于任何其他目的,否则你可能会不小心清除稍后需要的值。
循环的有趣和有用之处在于,它们改变程序“控制流”的方式。通常我们认为计算机是严格按顺序执行一系列指令。引入循环会导致Python退回去并重复执行一些语句。类似for循环的语句称为“控制结构”,因为它们控制程序其他部分的执行。
一些程序员发现,用图片的方式来思考控制结构是有帮助的,即所谓的“流程图”。流程图用一些框来表示程序的不同部分,并用框之间的箭头表示程序运行时的事件序列。图3用流程图描述了for循环的语义。
图3 for循环的流程图
如果你在理解for循环时遇到困难,可能会发现学习流程图很有用。流程图中的菱形框表示程序中的决定。当Python遇到循环头时,它检查序列中是否有项。如果答案为“是”,则循环索引变量被赋予序列中的下一项,然后执行循环体。一旦循环体完成,程序返回到循环头并检查序列中的下一个值。如果没有更多的项,循环就退出,程序移动到循环之后的语句。
示例程序:终值
我们用另一个编程过程的例子来结束本文。我们希望开发一个程序来确定投资的终值。我们将从对问题的分析开始。你知道存入银行账户的钱会赚取利息,这个利息随着时间的推移而累积。从现在起10年后,一个账户将有多少钱?显然,这取决于我们开始有多少钱(本金)以及账户赚多少利息。给定本金和利率,程序应该能够计算未来10年投资的终值。
我们继续制定程序的确切规格说明。记住,这是程序做什么的描述。输入应该是什么?我们需要用户输入初始投资金额,即本金。我们还需要说明账户赚多少利息。这取决于利率和计复利的频率。处理此问题的一种简单方法是让用户输入年度百分比率。无论实际利率和复利频率如何,年利率告诉我们一年内的投资收益。如果年利率为3%,那么100美元的投资将在一年的时间内增长到103美元。用户应如何表示年利率3%?有一些合理的选择。让我们假设用户提供一个小数,因此利率将输入为0.03。
这样就得到以下规格说明:
该程序中的所有语句类型都已在本文中详细讨论过。如果有任何问题,请回头查看相关说明。特别要注意的是,计数循环模式用于应用10次利息公式。
就到这里了。下面是完成的程序:
注意,我添加了几个空行来分隔程序的输入、处理和输出部分。策略性地放置“空行”能让程序更具有可读性。
这就是我所举的例子,测试和调试是留给你的练习。