几乎每个人都用过计算机。也许你玩过计算机游戏,或曾用计算机写文章、在线购物、听音乐,或通过社交媒体与朋友联系。计算机被用于预测天气、设计飞机、制作电影、经营企业、完成金融交易和控制工厂等。
你是否停下来想过,计算机到底是什么?一个设备如何能执行这么多不同的任务?学习计算机和计算机编程就从这些基本问题开始。
现代计算机可以被定义为“在可改变的程序的控制下,存储和操纵信息的机器”。该定义有两个关键要素。第一,计算机是用于操纵信息的设备。这意味着我们可以将信息放入计算机,它可以将信息转换为新的、有用的形式,然后输出或显示信息,让我们解释。
第二,计算机不是唯一能操纵信息的机器。当你用简单的计算器来加一组数字时,就在输入信息(数字),计算器就在处理信息,计算连续的总和,然后显示。另一个简单的例子是油泵。给油箱加油时,油泵利用某些输入:当前每升汽油的价格和来自传感器的信号,读取汽油流入汽车油箱的速率。油泵将这个输入转换为加了多少汽油和应付多少钱的信息。
我们不会将计算器或油泵看作完整的计算机,尽管这些设备的现代版本实际上可能包含嵌入式计算机。它们与计算机不同,它们被构建为执行单个特定任务。这就是定义的第二部分出现的地方:计算机在可改变的程序的控制下运行。这到底是什么意思?
“计算机程序”是一组详细的分步指令,告诉计算机确切地做什么。如果我们改变程序,计算机就会执行不同的动作序列,因而执行不同的任务。正是这种灵活性,让计算机在一个时刻是文字处理器,在下一个时刻是金融顾问,后来又变成一个街机游戏。机器保持不变,但控制机器的程序改变了。
每台计算机只是“执行”(运行)程序的机器。有许多不同种类的计算机。你可能熟悉Macintosh、PC、笔记本计算机、平板计算机和智能手机,但不论实际上还是理论上,都有数千种其他类型的计算机。计算机科学有一个了不起的发现,即认识到所有这些不同的计算机具有相同的力量,通过适当的编程,每台计算机基本上可以做任何其他计算机可以做的事情。在这个意义上说,放在你的办公桌上的PC实际上是一台通用机器。它可以做任何你想要它做的事,只要你能足够详细地描述要完成的任务。现在它是一台强大的机器!
你已经知道了计算的一个要点:“软件”(程序)主宰“硬件”(物理机器)。软件决定计算机可以做什么。没有软件,计算机只是昂贵的镇纸。创建软件的过程称为“编程”,这是本书的主要关注点。
计算机编程是一项具有挑战性的活动。良好的编程既要有全局观,又要注意细节。不是每个人都有天赋成为一流的程序员,正如不是每个人都具备成为专业运动员的技能。然而,几乎任何人都可以学习如何为计算机编程。只要有一点耐心和努力,本书将帮助你成为一名程序员。
学习编程有很多好理由。编程是计算机科学的一个基本组成部分,因此对所有立志成为计算机专业人员的人都很重要。但其他人也可以从编程经验中受益。计算机已经成为我们社会中的常见工具。要理解这个工具的优点和局限性,就需要理解编程。非程序员经常觉得他们是计算机的奴隶。然而,程序员是真正的控制者。如果你希望成为一个更聪明的计算机用户,本书就是为你准备的。
编程也有很多乐趣。这是一项智力活动,让人们通过有用的、有时非常漂亮的创作来表达自己。不管你信不信,许多人确实爱好编写计算机程序。编程也会培养有价值的问题解决技能,特别是将复杂系统分解为一些可理解的子系统及其交互,从而分析复杂系统的能力。
你可能知道,程序员有很大的市场需求。不少文科生已经将一些计算机编程课程作为一种有利可图的职业选择。计算机在当今的商业世界中如此常见,以至于理解计算机和编程的能力可能就会让你在竞争中占据优势,不论你是何种职业。灵感迸发时,你就准备好写出下一个杀手级应用程序了。
你可能会惊讶地得知,计算机科学不是研究计算机的。著名计算机科学家Edsger Dijkstra曾经说过,计算机之于计算机科学,正如望远镜之于天文学。计算机是计算机科学中的重要工具,但它本身不是研究的对象。由于计算机可以执行我们描述的任何过程,所以真正的问题是:“我们可以描述什么过程?”换句话说,计算机科学的根本问题就是“可以计算什么”。计算机科学家利用许多研究技术来回答这个问题。其中三种主要技术是设计、分析和实验。
证明某个问题可以解决的一种方式就是实际设计解决方案。也就是说,我们开发了一个逐步的过程,以实现期望的结果。计算机科学家称之为“算法”。这是一个奇特的词,基本上意味着“菜谱”。算法设计是计算机科学中最重要的方面之一。在本书中,你会看到设计和实现算法的技术。
设计有一个弱点,它只能回答“什么是可计算的”。如果可以设计一个算法,那么问题是可解的。然而,未能找到算法并不意味着问题是不可解的。这可能意味着我只是不够聪明,或者碰巧还没有找到正确的想法。这就是引入分析的原因。
分析是以数学方式检查算法和问题的过程。计算机科学家已经指出,一些看似简单的问题不能通过任何算法解决。另一些问题是“难解的”(intractable)。解决这些问题的算法需要太长时间,或者需要太多存储器,因而没有实际价值。算法分析是计算机科学的重要组成部分,在整本书中,我们将探讨一些基本原则。第13章有不可解决和难解问题的例子。
一些问题太复杂或定义不明确,无法分析。在这种情况下,计算机科学家就依靠实验。他们实际实现一些系统,然后研究结果的行为。即使在进行理论分析时,也经常需要实验来验证和完善分析。对于大多数问题,底线是能否构建一个可靠的工作系统。通常我们需要对系统进行经验性测试,以确定这个底线已经满足。当你开始编写自己的程序时,会有很多机会观察你的解决方案的表现。
我已经从设计、分析和评估算法的角度定义了计算机科学,这当然是该学科的核心。然而,当今计算机科学家参与广泛的活动,所有这些活动都在计算这把大伞之下。一些例子包括移动计算、网络、人机交互、人工智能、计算科学(使用强大的计算机来模拟科学过程)、数据库和数据挖掘、软件工程、网络和多媒体设计、音乐制作、管理信息系统和计算机安全。无论在何处进行计算,计算机科学的技能和知识都有应用。
你不必知道计算机工作的所有细节,也能成为一名成功的程序员,但了解基本原理将有助于掌握让程序运行所需的步骤。这有点像驾驶汽车。了解一点内燃机知识,有助于解释为什么必须做一些事情,如加油、点火、踩油门等。你可以通过记住要做什么来学习驾驶,但拥有更多知识会让整个过程更容易理解。让我们花一点时间来看看计算机的内部构造。
虽然不同计算机在具体细节上会显著不同,但在更高的层面上,所有现代数字计算机是非常相似的。图1.1展示了计算机的功能视图。中央处理单元(CPU)是机器的“大脑”。这是计算机执行所有基本操作的地方。CPU可以执行简单的算术运算,如两个数相加,也可以执行逻辑操作,如测试两个数是否相等。
图1.1 计算机的功能视图
存储器存储程序和数据。CPU只能直接访问存储在“主存储器”(称为RAM,即随机存取存储器)中的信息。主存储器速度快,但它也是易失性存储。也就是说,当电源关闭时,存储器中的信息会丢失。因此,还必须有一些辅助存储器,提供永久的存储。
在现代个人计算机中,主要的辅助存储器通常是内部的硬盘驱动器(HDD)或固态驱动器(SSD)。HDD将信息以磁模式存储在旋转磁盘上,而SSD使用称为闪存的电子电路。大多数计算机还支持可移动介质作为辅助存储器,如USB存储“棒”(也是一种形式的闪存)和DVD数字多功能光盘,后者以光学模式存储信息,由激光读取和写入。
人类通过输入和输出设备与计算机交互。你可能熟悉常见的设备,如键盘、鼠标和显示器(视频屏幕)。来自输入设备的信息由CPU处理,并可以被移动到主存储器或辅助存储器。类似地,需要显示信息时,CPU将它发送到一个或多个输出设备。
那么,你启动最喜欢的游戏或文字处理程序时,会发生什么?构成程序的指令从(更)持久的辅助存储器复制到计算机的主存储器中。一旦指令被加载,CPU就开始执行程序。
技术上,CPU遵循的过程称为“读取—执行循环”。从存储器取得第一条指令,解码以弄清楚它代表什么,并且执行适当的动作。然后,取得并解码和执行下一条指令。循环继续,指令接着指令。这确实是所有的计算机从你打开它直到再次关闭时做的事情:读取指令、解码、执行。这看起来不太令人兴奋,是吗?但计算机能以惊人的速度执行这个简单的指令流,每秒完成数十亿条指令。将足够多的简单指令以正确的方式放在一起,计算机完成了惊人的工作。
请记住,程序只是一系列指令,告诉计算机做什么。显然,我们需要用计算机可以理解的语言来提供这些指令。如果可以用我们的母语告诉计算机做什么,就像科幻电影中那样,当然很好。(“计算机,曲速引擎全速到达行星Alphalpha需要多长时间?”)计算机科学家在这个方向上取得了长足的进步。你可能熟悉Siri(Apple)、Google Now(Android)和Cortana(Microsoft)等技术。但是,所有认真使用过这种系统的人都可以证明,设计一个完全理解人类语言的计算机程序仍然是一个未解决的问题。
即使计算机可以理解我们,人类语言也不太适合描述复杂的算法。自然语言充满了模糊和不精确。例如,如果我说“I saw the man in the park with the telescope”,是我拥有望远镜,还是那个人拥有望远镜?谁在公园里?我们大多数时间都相互理解,因为所有人都拥有广泛的共同知识和经验。但即便如此,误解也是很常见的。
计算机科学家已经设计了一些符号,以准确无二义的方式来表示计算,从而绕过了这个问题。这些特殊符号称为编程语言。编程语言中的每个结构都有精确的形式(它的“语法”)和精确的含义(它的“语义”)。编程语言就像一种规则,用于编写计算机将遵循的指令。实际上,程序员通常将他们的程序称为“计算机代码”(computer code),用编程语言来编写算法的过程被称为“编码”(coding)。
Python是一种编程语言,它是我们在本书中使用的语言[1]。你可能已经听说过其他一些常用的语言,如C ++、Java、Javascript、Ruby、Perl、Scheme和BASIC。计算机科学家已经开发了成千上万种编程语言,而且语言本身随着时间演变,产生多个、有时非常不同的版本。虽然这些语言在许多细节上不同,但它们都有明确定义的、无二义的语法和语义。
上面提到的所有语言都是高级计算机语言的例子。虽然它们是精确的,但它们的设计目的是让人使用和理解。严格地说,计算机硬件只能理解一种非常低级的语言,称为“机器语言”。
假设我们希望让计算机对两个数求和。CPU实际执行的指令可能是这样的:
将内存位置2001的数加载到CPU中
将内存位置2002的数加载到CPU中
在CPU中对这两个数求和
将结果存储到位置2003
两个数求和似乎有很多工作,不是吗?实际上,它甚至比这更复杂,因为指令和数字以二进制符号表示(即0和1的序列)。
在Python这样的高级语言中,两个数求和可以更自然地表达为c = a + b。这让我们更容易理解,但我们需要一些方法,将高级语言翻译成计算机可以执行的机器语言。有两种方法可以做到这一点:高级语言可以被“编译”或“解释”。
“编译器”是一个复杂的计算机程序,它接受另一个以高级语言编写的程序,并将其翻译成以某个计算机的机器语言表达的等效程序。图1.2展示了编译过程的框图。高级程序被称为“源代码”,得到的“机器代码”是计算机可以直接执行的程序。图中的虚线表示机器代码的执行(也称为“运行程序”)。
图1.2 编译高级语言
“解释器”是一个程序,它模拟能理解高级语言的计算机。解释器不是将源程序翻译成机器语言的等效程序,而是根据需要一条一条地分析和执行源代码指令。图1.3展示了这个过程。
图1.3 解释高级语言
解释和编译之间的区别在于,编译是一次性翻译。一旦程序被编译,它可以重复运行而不需要编译器或源代码。在解释的情况下,每次程序运行时都需要解释器和源代码。编译的程序往往更快,因为翻译是一次完成的,但是解释语言让它们拥有更灵活的编程环境,因为程序可以交互式开发和运行。
翻译过程突出了高级语言对机器语言的另一个优点:可移植性。计算机的机器语言由特定CPU的设计者创建。每种类型的计算机都有自己的机器语言。笔记本计算机中的Intel i7处理器程序不能直接在智能手机的ARMv8 CPU上运行。不同的是,以高级语言编写的程序可以在许多不同种类的计算机上运行,只要存在合适的编译器或解释器(这只是另一个程序)。因此,我可以在我的笔记本计算机和平板计算机上运行完全相同的Python程序。尽管它们有不同的CPU,但都运行着Python解释器。
在你已了解了所有技术细节后,就可以开始享受Python的乐趣了。最终的目标是让计算机按我们的要求办事。为此,我们将编写控制机器内部计算过程的程序。你已经看到,这个过程中没有魔法,但编程的某些方面让人感觉像魔法。
计算机内部的计算过程就像一些魔法精灵,我们可以利用它们为我们工作。不幸的是,这些精灵只能理解一种非常神秘的语言,而我们不懂。我们需要一个友好的小仙子,能指导这些精灵实现我们的愿望。我们的小仙子是一个Python解释器。我们可以向Python解释器发出指令,并指导下面的精灵来执行我们的需求。我们通过一种特殊的法术和咒语(即Python)与小仙子沟通。开始学习Python的最好方法是将我们的小仙子放出瓶子,尝试一些法术。
对于大多数Python安装,你可以用交互模式启动Python解释器,这称为shell。shell允许你键入Python命令,然后显示执行它们的结果。启动shell的具体细节因不同安装而异。如果你使用来自www.python.org的PC或Mac的标准Python发行版,应该有一个名为IDLE的应用程序,它提供了Python shell,正如我们稍后会看到,它还可以帮助你创建和编辑自己的Python程序。本书的支持网站提供了在各种平台上安装和使用Python的信息。
当你第一次启动IDLE(或另一个Python shell),应该看到如下信息:
Python 3.4.3 (v3.4.3:9b73f1c3e601, Feb 24 2015, 22:43:06)
[MSC v.1600 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
>>>
确切的启动消息取决于你正在运行的Python版本和你正在使用的系统。重要的部分是最后一行。>>>是一个Python提示符,表示我们的小仙子(Python解释器)正在等待我们给它一个命令。在编程语言中,一个完整的命令称为语句。
下面是与Python shell交互的例子:
>>> print("Hello, World!")
Hello, World!
>>> print(2 + 3)
5
>>> print("2 + 3 =", 2 + 3)
2 + 3 = 5
这里,我尝试了三个使用Python的print语句的例子。第一个print语句要求Python显示文本短语Hello, World!。Python在下一行做出响应,打印出该短语。第二个print语句要求Python打印2与3之和。第三个print结合了这两个想法。Python打印出引号中的部分“2 + 3 =”,然后是2 + 3的结果,即5。
这种shell交互是在Python中尝试新东西的好方法。交互式会话的片段散布在本书中。如果你在示例中看到Python提示符>>>,这就告诉你正在展示交互式会话。启动自己的Python shell并尝试这些例子,是一个好主意。
通常,我们希望超越单行的代码片段,并执行整个语句序列。Python允许我们将一系列语句放在一起,创建一个全新的命令或函数。下面的例子创建了一个名为hello的新函数:
>>> def hello():
print("Hello")
print("Computers are fun!")
>>>
第一行告诉Python,我们正在定义一个新函数,命名为hello。接下来两行缩进,表明它们是hello函数的一部分。(注意:有些shell会在缩进行的开头打印省略号[“...”])。最后的空白行(通过按两次<Enter>键获得)让Python知道定义已完成,并且shell用另一个提示符进行响应。注意,键入定义并不会导致Python打印任何东西。我们告诉Python,当hello函数用作命令时应该发生什么,但实际上并没有要求Python执行它。
键入函数名称并跟上括号,函数就被调用了。下面是使用hello命令时发生的事情:
>>> hello()
Hello
Computers are fun!
>>>
你看到这完成了什么?hello函数定义中的两个print语句按顺序执行了。
你可能对定义中的括号和hello的使用感到好奇。命令可以有可变部分,称为参数(也称为变元),放在括号中。让我们看一个使用参数、自定义问候语的例子。先是定义:
>>> def greet(person):
print("Hello", person)
print("How are you?")
现在我们可以使用定制的问候。
>>> greet("John")
Hello John
How are you?
>>> greet("Emily")
Hello Emily
How are you?
>>>
你能看到这里发生了什么吗?使用greet时,我们可以发送不同的名称,从而自定义结果。你可能也注意到,这看起来类似于之前的print语句。在Python中,print是一个内置函数的例子。当我们调用print函数时,括号中的参数告诉函数要打印什么。
我们将在后面详细讨论参数。目前重要的是要记住,执行一个函数时,括号必须包含在函数名之后。即使没有给出参数也是如此。例如,你可以使用print而不使用任何参数,创建一个空白的输出行。
>>> print()
>>>
但是如果你只键入函数的名称,省略括号,函数将不会真正执行。相反,交互式Python会话将显示一些输出,表明名称所引用的函数,如下面的交互所示:
>>> greet
<function greet at 0x8393aec>
>>> print
<built-in function print>
有趣的文本0x8393aec是在计算机存储器中的位置(地址),其中恰好存储了greet函数的定义。如果你在自己的计算机上尝试,几乎肯定会看到不同的地址。
将函数交互式地输入到Python shell中,像我们的hello和greet示例那样,这存在一个问题:当我们退出shell时,定义会丢失。如果我们下次希望再次使用它们,必须重新键入。程序的创建通常是将定义写入独立的文件,称为“模块”或“脚本”。此文件保存在辅助存储器中,所以可以反复使用。
模块文件只是一个文本文件,你可以用任何应用程序来编辑文本,例如记事本或文字处理程序,只要将程序保存为“纯文本”文件即可。有一种特殊类型的应用称为集成开发环境(IDE),它们简化了这个过程。IDE专门设计用于帮助程序员编写程序,包括自动缩进、颜色高亮显示和交互式开发等功能。IDLE是一个很好的例子。到目前为止,我们只将IDLE作为一个Python shell,但它实际上是一个简单却完整的开发环境[2]。
让我们编写并运行一个完整的程序,从而说明模块文件的使用。我们的程序将探索一个被称为混沌(chaos)的数学概念。要将此程序键入IDLE,应选择File/New File菜单选项。这将打开一个空白(非shell)窗口,你可以在其中键入程序。下面是程序的Python代码:
# File: chaos.py
# A simple program illustrating chaotic behavior.
def main():
print("This program illustrates a chaotic function")
x = eval(input("Enter a number between 0 and 1: "))
for i in range(10):
x = 3.9 * x * (1 - x)
print(x)
main()
键入它之后,从菜单中选择File/Save,并保存为chaos.py。扩展名.py表示这是一个Python模块。在保存程序时要小心。有时IDLE默认会在系统范围的Python文件夹中启动。要确保导航到你保存自己文件的文件夹。我建议将所有Python程序放在一个专用的文件夹中,放在你自己的个人文档目录中。
此时,你可能正试图理解刚刚输入的内容。你可以看到,这个特定的例子包含了几行代码,定义了一个新函数main。(程序通常放在一个名为main的函数中。)文件的最后一行是调用此函数的命令。如果你不明白main实际上做了什么,也不要担心,我们将在下一节中讨论它。这里的要点在于,一旦我们将一个程序保存在这样的模块文件中,就可以随时运行它。
我们的程序能以许多不同的方式运行,这取决于你使用的实际操作系统和编程环境。如果你使用的是窗口系统,则可以通过单击(或双击)模块文件的图标来运行Python程序。在命令行情况下,可以键入像python chaos.py这样的命令。使用IDLE时,只需从模块窗口菜单中选择Run/Run Module即可运行程序。按下<F5>键是该操作的方便快捷方式。
IDLE运行程序时,控制将切换到shell窗口。下面是看起来的样子:
>>> ======================= RESTART =======================
>>>
This program illustrates a chaotic function
Enter a number between 0 and 1: .25
0.73125
0.76644140625
0.6981350104385375
0.8218958187902304
0.5708940191969317
0.9553987483642099
0.166186721954413
0.5404179120617926
0.9686289302998042
0.11850901017563877
>>>
第一行是来自IDLE的通知,表明shell已重新启动。IDLE在每次运行程序时都会这样做,这样程序就运行在一个干净的环境中。Python然后从上至下逐行运行该模块。这就像我们在交互式Python提示符下逐行键入它们一样。模块中的def会导致Python创建main函数。这个模块的最后一行导致Python调用main函数,从而运行我们的程序。正在运行的程序要求用户输入一个介于0和1之间的数字(在这个例子中,我键入“.25”),然后打印出10个数字的序列。
如果浏览计算机上的文件,你可能会注意到,Python有时会在存储模块文件的文件夹中创建另一个名为pycache的文件夹。这里是Python存储伴随文件的地方,伴随文件的扩展名为.pyc。在本例中,Python可能会创建另一个名为chaos.pyc的文件。这是Python解释器使用的中间文件。从技术上讲,Python采用混合编译/解释的过程。模块文件中的Python源代码被编译为较原始的指令,称为字节代码。然后解释这个字节代码(.pyc)。如果有.pyc文件可用,则第二次运行模块就会更快。但是,如果要节省磁盘空间,你可以删除字节代码文件。Python会根据需要自动重新创建它们。
在IDLE下运行模块,会将程序加载到shell窗口中。你可以要求Python执行main命令,从而再次运行该程序。只需在shell提示符下键入命令。继续我们的例子,下面是我们重新运行程序时它的样子,以“.26”作为输入:
>>> main()
This program illustrates a chaotic function
Enter a number between 0 and 1: .26
0.75036
0.73054749456
0.767706625733
0.6954993339
0.825942040734
0.560670965721
0.960644232282
0.147446875935
0.490254549376
0.974629602149
>>>
chaos程序的输出可能看起来不太令人兴奋,但它说明了物理学家和数学家已知的一个非常有趣的现象。让我们逐行来看这个程序,看看它做了什么。不要担心不能马上理解每个细节,我们将在下一章重新探讨所有这些想法。
程序的前两行以#字符开头:
# File: chaos.py
# A simple program illustrating chaotic behavior.
这些行称为“注释”。它们是为程序的人类读者编写的,会被Python忽略。Python解释器总是跳过从井号(#)到行末之间的所有文本。
程序的下一行开始定义一个名为main的函数:
def main():
严格地说,不需要创建main函数。因为模块的代码行在加载时会被执行,所以我们可以在没有这个定义的情况下编写我们的程序。也就是说,模块可能看起来像下面这样:
# File: chaos.py
# A simple program illustrating chaotic behavior.
print("This program illustrates a chaotic function")
x = eval(input("Enter a number between 0 and 1: "))
for i in range(10):
x = 3.9 * x * (1 - x)
print(x)
这个版本更短一些,但惯例是将包含程序的指令放在main函数内部。上面展示了这种方法的一个直接好处:它允许我们通过调用main()来运行程序。我们不必重新启动Python shell就能再次运行它,这在没有main的情况下是不行的。
main内部的第一行是程序真正的开始。
print("This program illustrates a chaotic function")
这行导致Python打印一个消息,在程序运行时介绍它自己。
看看程序的下一行:
x = eval(input("Enter a number between 0 and 1: "))
这里的x是变量的示例。变量为值提供了一个名称,以便我们在程序的其他位置引用它。
整行是一个语句,从用户那里获得一些输入。这一行内容有点多,我们将在下一章讨论它的细节。现在,你只需要知道它完成了什么。当Python遇到该语句时,它显示引号内的消息“Enter a number between 0 and 1:”并暂停,等待用户在键盘上键入内容,然后按<Enter>键。随后用户键入的值保存为变量x。在上面显示的第一个例子中,用户输入了“.25”,它成为x的值。
下一个语句是循环的示例。
for i in range(10):
循环是一种策略,它告诉Python重复做同样的事情。这个特定的循环说要做某事10次。在循环头下缩进的几行,是要执行10次的语句。它们构成了循环体。
x = 3.9 * x * (1 - x)
print(x)
循环的效果完全一样,就像我们将循环体写了10次:
x = 3.9 * x * (1 - x)
print(x)
x = 3.9 * x * (1 - x)
print(x)
x = 3.9 * x * (1 - x)
print(x)
x = 3.9 * x * (1 - x)
print(x)
x = 3.9 * x * (1 - x)
print(x)
x = 3.9 * x * (1 - x)
print(x)
x = 3.9 * x * (1 - x)
print(x)
x = 3.9 * x * (1 - x)
print(x)
x = 3.9 * x * (1 - x)
print(x)
x = 3.9 * x * (1 - x)
print(x)
显然,使用循环为程序员省却了很多麻烦。
但是这些语句究竟做了什么?第一句执行了计算:
x = 3.9 * x * (1 - x)
这被称为“赋值”语句。“=”右侧的部分是一个数学表达式。Python使用“*”字符表示乘法。回想一下,x的值是0.25(来自上面的input)。计算的值为3.9(0.25)(1 – 0.25),即0.73125。一旦计算出右侧的值,它就被保存为(或赋值给)出现在“=”左侧的变量,在这个例子中是x。x的新值(0.73125)替换了旧值(0.25)。
循环体中的第二行是我们之前遇到的一种语句类型,即print语句。
print(x)
Python执行此语句时,屏幕上将显示x的当前值。所以第一个输出的数是0.73125。
记住,循环要执行10次。打印x的值后,循环的两个语句再次执行。
x = 3.9 * x * (1 - x)
print(x)
当然,现在x的值为0.73125,所以公式计算新的x值为3.9(0.73125)(1 – 0.73125),它是0.76644140625。
你能看到每次循环时如何用x的当前值来计算一个新值吗?这是示例运行中数字的来源。你可以针对一个不同的输入值(例如0.5)尝试执行程序的步骤,然后用Python运行该程序,看看你模拟计算机的情况。
我在前面说过,chaos程序展示了一个有趣的现象。满屏幕的数字哪里有趣?如果你自己尝试这个程序会发现,无论从什么数字开始,结果总是相似的:程序吐出10个似乎随机的数字,在0和1之间。随着程序运行,x的值似乎跳来跳去,好吧,像混沌一样。
由该程序计算的函数具有一般形式k(x)(1–x),k在这个例子中是3.9。这被称为逻辑函数。它模拟某些类型的不稳定电子电路,并且有时也在限制条件下模拟群体变化。重复应用逻辑函数可以产生混沌。虽然我们的程序有一个明确的底层行为,但输出似乎不可预测。
混沌函数有一个有趣的属性,即随着公式被重复应用,初始值的非常小的差异可以导致结果的巨大差异。你可以在chaos程序中看到这一点,只需输入稍微不同的数字。以下是修改后的程序的输出,显示了初始值为0.25和0.26的结果:
input 0.25 0.26
---------------------------
0.731250 0.750360
0.766441 0.730547
0.698135 0.767707
0.821896 0.695499
0.570894 0.825942
0.955399 0.560671
0.166187 0.960644
0.540418 0.147447
0.968629 0.490255
0.118509 0.974630
使用非常相似的起始值,输出在几个迭代中保持相似,然后就显著不同了。大约到第五次迭代,两个模型之间就似乎没有任何关系了。
我们的chaos程序的这两个特征,即显然不可预测性和对初始值的极端敏感性,是混沌行为的标志。混沌对计算机科学有重要的影响。事实证明,在现实世界中,我们可能希望用计算机建模和预测的许多现象就是这种混沌行为。你可能听说过所谓的蝴蝶效应。用于模拟和预测天气模式的计算机模型是如此敏感,以至于一只蝴蝶在新泽西拍打翅膀,可能会影响伊利诺州皮奥里亚(Peoria)是否下雨的预测。
很可能即使有完美的计算机建模,我们也永远不能准确地测量已有的天气条件,从而提前几天预测天气。测量就是不够精确,不能让预测在较长时间内准确。
如你所见,这个小程序给计算机用户上了有价值的一课。尽管计算机如此神奇,但它们给出的结果只是与程序所基于的数学模型一样有用。计算机可能由于程序错误而给出不正确的结果,但如果模型错误或初始输入不够精确,即使正确的程序也可能产生错误的结果。
本章介绍了计算机、计算机科学和编程。下面是一些关键概念的小结。
1.计算机科学是计算机的研究。
2.CPU是计算机的“大脑”。
3.辅助存储器也称为RAM。
4.计算机当前正在处理的所有信息都存储在主存储器中。
5.语言的语法是它的意思,语义是它的形式。
6.函数定义是定义新命令的语句序列。
7.编程环境是指程序员工作的地方。
8.变量用于给一个值赋予一个名称,这样它就可以在其他地方被引用。
9.循环用于跳过程序的一部分。
10.混沌函数不能由计算机计算。
1.计算机科学的根本问题是 。
a.计算机的计算速度有多快
b.可以计算什么
c.什么是最有效的编程语言
d.程序员可以赚多少钱
2.算法类似于 。
a.报纸
b.捕蝇草
c.鼓
d.菜谱
3.一个问题是难解的,如果 。
a.你不能反转其解决方案
b.涉及拖拉机
c.它有很多解决方案
d.解决它不实际
4.以下 项不是辅助存储器。
a.RAM
b.硬盘驱动器
c.USB闪存驱动器
d.DVD
5.设计来让人类使用和理解的计算机语言是 。
a.自然语言
b.高级计算机语言
c.机器语言
d.提取—执行语言
6.语句是 。
a.机器语言的翻译
b.完整的计算机命令
c.问题的精确描述
d.算法的一部分
7.编译器和解释器之间的一个区别是 。
a.编译器是一个程序
b.使用编译器将高级语言翻译成机器语言
c.在程序翻译之后不再需要编译器
d.编译器处理源代码
8.按照惯例,程序的语句通常放在一个函数中,该函数名为 。
a.import
b.main
c.program
d.IDLE
9.关于注释,以下不正确的是 。
a.它们让程序更有效率
b.它们是为人类读者
c.它们被Python忽略
d.在Python中,它们以井号(#)开头
10.函数定义的括号中列出的项被称为 。
a.括号
b.参数
c.变元
d.b和c项都是正确的
1.比较并对比本章中的以下概念对。
a.硬件与软件
b.算法与程序
c.编程语言与自然语言
d.高级语言与机器语言
e.解释器与编译器
f.语法与语义
2.列出图1.1中计算机的5个基本功能单元,并用你自己的话并解释它们的作用。
3.写一个制作花生酱和果冻三明治(或其他日常活动)的详细算法。你应该假设正在与一个概念上能够完成该任务,但从来没有实际做过的人交谈。例如,你可能告诉一个小孩子怎么做。
4.正如你将在后续章节中学到的,存储在计算机中的许多数字不是精确的值,而是接近的近似值。例如,值0.1可能存储为0.10000000000000000555。通常,这样小的差异不是问题。然而,考虑到你在第1章中学到的混沌行为,你应该意识到在某些情况下需要谨慎。你能想到这可能是一个问题的例子吗?请说明。
5.使用0.15作为输入值,手动追踪第1.6节中的chaos程序。显示结果的输出序列。
1.启动交互式Python会话,并尝试键入以下每个命令。写下你看到的结果。
a.print("Hello, world!")
b.print("Hello", "world!")
c.print(3)
d.print(3.0)
e.print(2 + 3)
f.print(2.0 + 3.0)
g.print("2" + "3")
h.print("2 + 3 =", 2 + 3)
i.print(2 * 3)
j.print(2 ** 3)
k.print(7 / 3)
l.print(7 // 3)
2.输入并运行第1.6节中的chaos
程序。尝试使用各种输入值,观察它在本章中描述的功能。
3.修改chaos
程序,使用2.0代替3.9作为逻辑函数中的乘数。你修改的代码行应该像下面这样:
x = 2.0 * x * (1 - x)
用各种输入值运行该程序,并将结果与从原始程序获得的结果进行比较。写一小段话,描述你在两个版本的行为中观察到的所有差异。
4.修改chaos
程序,让它打印出20个值,而不是10个。
5.修改chaos
程序,让打印值的数量由用户确定。你将必须在程序顶部附近添加一行,从用户获取另一个值:
n = eval(input("How many numbers should I print? "))
然后,你需要更改循环,使用n代替具体的数字。
6.在chaos
程序中执行的计算,可以用代数等价的多种方式来编写。为以下每种计算方式编写一个程序版本。让你修改的程序打印出100次迭代的计算,并比较相同输入的运行结果。
a.3.9 * x * (1 - x)
b.3.9 * (x - x * x)
c.3.9 * x - 3.9 * x * x
请解释这个实验的结果。提示:参见上面的讨论问题4。
7.(高级)修改chaos
程序,让它接受两个输入,然后打印一个包含两列的表,类似第1.8节中显示的表。(注意:你可能无法让列排得与示例中一样好,第5章将讨论如何使用固定小数位数打印数字。)
[1] 本书的这个版本使用Python 3.4版本开发和测试。Python 3.5现在可用。如果你的计算机上安装了早期版本的Python,则应升级到最新的稳定版3.x,以便尝试这些例子。
[2] 事实上,IDLE代表Integrated DeveLopment Environment。多出来的“L”是对Eric Idle的致敬,因为Monty Python的名望。
正如你在上一章中看到的,运行已经编写的程序很容易。较难的部分实际上是先得到一个程序。计算机是非常实在的,必须告诉它们要做什么,直至最后的细节。编写大型程序是一项艰巨的挑战。如果没有系统的方法,几乎是不可能的。
创建程序的过程通常被分成几个阶段,依据是每个阶段中产生的信息。简而言之,你应该做以下工作。
分析问题 确定要解决的问题是什么。尝试尽可能多地了解它。除非真的知道问题是什么,否则就不能开始解决它。
确定规格说明 准确描述程序将做什么。此时,你不必担心程序“怎么做”,而是要确定它“做什么”。对于简单程序,这包括仔细描述程序的输入和输出是什么以及它们的相互关系。
创建设计 规划程序的总体结构。这是描述程序怎么做的地方。主要任务是设计算法来满足规格说明。
实现设计 将设计翻译成计算机语言并放入计算机。在本书中,我们将算法实现为Python程序。
测试/调试程序 试用你的程序,看看它是否按预期工作。如果有任何错误(通常称为“缺陷”),那么你应该回去修复它们。定位和修复错误的过程称为“调试”程序。在调试阶段,你的目标是找到错误,所以应该尝试你能想到的“打破”程序的一切可能。记住这句老格言:“没有什么能防住人犯傻,因为傻子太聪明了。”
维护程序 继续根据用户的需求开发该程序。大多数程序从来没有真正完成,它们在多年的使用中不断演进。
让我们通过一个真实世界的简单例子,来体验软件开发过程的步骤,其中涉及一个虚构的计算机科学学生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的完整算法:
输入摄氏度温度(称为celsius)
计算华氏度为(9/5)celsius + 32
输出华氏度
下一步是将此设计转换为Python程序。这很直接,因为算法的每一行都变成了相应的Python代码行。
# convert.py
# A program to convert Celsius temps to Fahrenheit
# by: Susan Computewell
def main():
celsius = eval(input("What is the Celsius temperature? "))
fahrenheit = 9/5 * celsius + 32
print("The temperature is", fahrenheit, "degrees Fahrenheit.")
main()
看看你是否能弄清楚这个程序的每一行做了什么。如果一些部分不是很清楚,也不要担心,下一节将详细讨论。
完成程序后,Susan测试它,看看它工作得如何。她使用她知道正确答案的输入。下面是两个测试的输出:
What is the Celsius temperature? 0
The temperature is 32.0 degrees Fahrenheit.
What is the Celsius temperature? 100
The temperature is 212.0 degrees Fahrenheit.
你可以看到,Susan用值0和100来测试她的程序。看起来不错,她对解决方案感到满意。她特别高兴的是,似乎没有必要调试(这很不寻常)。
既然已经知道了编程过程,你就“几乎”准备好开始自己编写程序了。在此之前,你需要更完整的基础,了解Python的基本知识。接下来的几节将讨论一些技术细节,这对编写正确程序至关重要。这种材料看起来有点乏味,但你必须掌握这些基础,然后再进入更有趣的领域。
你已经看到,名称是编程的重要组成部分。我们为模块命名(例如convert),也为模块中的函数命名(例如main)。变量用于为值命名(例如celsius和fahrenheit)。从技术上讲,所有这些名称都称为“标识符”。Python对标识符的构成有一些规则。每个标识符必须以字母或下划线(“_”字符)开头,后跟字母、数字或下划线的任意序列。这意味着单个标识符不能包含任何空格。
根据上述规则,以下都是Python中的合法名称:
x
celsius
spam
spam2
SpamAndEggs
Spam_and_Eggs
标识符区分大小写,因此对Python来说,spam、Spam、sPam和SPAM是不同的名称。在大多数情况下,程序员可以自由选择符合这些规则的任何名称。好的程序员总是试图选择一些名字,它们能描述被命名的东西。
需要注意一件重要的事情:一些标识符是Python本身的一部分。这些名称称为“保留字”或“关键字”,不能用作普通标识符。Python关键字的完整列表如表2.1所列。
表2.1 Python关键字
False | class | finally | is | return |
None | continue | for | lambda | try |
True | def | from | nonlocal | while |
and | del | global | not | with |
as | elif | if | or | yield |
assert | else | import | pass | |
break | except | in | raise |
Python还包括相当多的内置函数,例如我们用过的print函数。虽然在技术上可以将内置的函数名称标识符用于其他目的,但这通常是一个“非常糟糕”的主意。例如,如果你重新定义print的含义,那么就无法再打印信息。你也会让所有阅读程序的Python程序员感到非常困惑,他们预期print指的是内置函数。内置函数的完整列表可在附录A中找到。
程序操作数据。到目前为止,我们已经在示例程序中看到了数字和文本两种不同类型的数据。我们将在后面的章节中详细讨论这些不同的数据类型。现在,你只需要记住,所有的数据必须以一些数字格式存储在计算机上,不同类型的数据以不同的方式存储。
产生或计算新数据值的程序代码片段称为“表达式”。最简单的表达式是字面量。字面量用于表示特定值。在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会计算表达式并打印出结果的文本表示。请考虑以下简短的交互:
>>> 32
32
>>> "Hello"
'Hello'
>>> "32"
'32'
请注意,当shell显示字符串的值时,它将字符序列放在单引号中。这样让我们知道该值实际上是文本而不是数字(或其他数据类型)。在最后一次交互中,我们看到表达式"32"产生一个字符串,而不是一个数字。在这种情况下,Python实际上是存储字符“3”和“2”,而不是数字32的表示。如果你现在不太明白,不要太担心。我们在后面的章节中讨论这些数据类型时,你的理解就会变得更加清晰。
一个简单的标识符也可以是一个表达式。我们使用标识符作为变量来给名字赋值。当标识符作为表达式出现时,它的值会被取出,作为表达式的结果。下面是与Python解释器的交互,展示了变量作为表达式:
>>> x = 5
>>> x
5
>>> print(x)
5
>>> print(spam)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'spam' is not defined
首先,变量x被赋值为5(使用数字字面量5)。在第二行交互中,我们要求Python对表达式x求值。作为响应,Python shell打印出5,这是刚才赋给x的值。当然,如果我们明确要求Python用print语句打印x,也会得到相同的结果。最后一个交互展示了如果尝试使用未赋值的变量,会发生什么。Python找不到值,所以它报告NameError。这说明没有该名称的值。这里的要点是,变量总是必须赋一个值,然后才能在表达式中使用。
较复杂、较有趣的表达式可以通过组合较简单的表达式和操作符来构造。对于数字,Python提供了一组标准的数学运算:加法、减法、乘法、除法和乘方。相应的Python运算符为“+”“-”“*”“/”和“**”。下面是一些来自chaos.py和convert.py的复杂表达式的例子:
3.9 * x * (1 - x)
9/5 * celsius + 32
空格在表达式中没有作用。最后一个表达式如果写成9/5*celsius+32,结果完全相同。通常,在表达式中加一些空格让它更容易阅读,是个好方法。
Python的数学运算符遵循的优先级和结合律,与你在数学课上学到的相同,包括使用括号来改变求值的顺序。在自己的程序中构建复杂表达式应该没什么困难。请记住,只有圆括号在数字表达式中是允许的。如果需要,可以嵌套使用它们,创建如下的表达式:
((x1 - x2) / 2*n) + (spam / k**3)
顺便说一句,Python还提供了字符串的运算符。例如,可以“加”字符串。
>>> "Bat" + "man"
'Batman'
这被称为“连接”。如你所见,效果是创建一个新的字符串,把两个字符串“粘”在一起。你将在第5章看到更多的字符串操作。
既然有了基本的构建块(标识符和表达式),你就可以更完整地描述各种Python语句。 你已经知道信息可以使用Python的内置函数print在屏幕上显示。到目前为止,我们已经看了几个例子,但我还没有详细解释打印功能。像所有的编程语言一样,Python对每个语句的语法(形式)和语义(意义)有一套精确的规则。计算机科学家已经开发了复杂的符号表示法,称为“元语言”,用于描述编程语言。在本书中,我们将依靠一个简单的模板符号表示法来说明各种语句的语法。
因为print是一个内置函数,所以print语句与任何其他函数调用具有相同的一般形式。我们键入函数名print,后面带上括号中列出的参数。下面是用我们的模板符号时print语句看起来的样子:
print(<expr>, <expr>, ..., <expr>)
print()
这两个模板展示了两种形式的print语句。第一个表示print语句可以包含函数名print,后面带上带括号的表达式序列,用逗号分隔。模板中的尖括号符号(<>)用于表示由Python代码的其他片段填充的“槽”。括号内的名称表示缺少什么,expr表示一个表达式。省略号(“...”)表示不确定的序列(在这个例子中是表达式)。你实际上不会输入圆点。第二个版本的print语句表明,不打印任何表达式的print也是合法的。
就语义而言,print语句以文本形式显示信息。所有提供的表达式都从左到右求值,结果值以从左到右的方式显示在输出行上。默认情况下,在显示的值之间放置一个空格字符。作为示例,下面print语句的序列:
print(3+4)
print(3, 4, 3 + 4)
print()
print("The answer is", 3 + 4)
产生的输出为:
7
3 4 7
The answer is 7
最后一个语句说明了,字符串字面量表达式如何经常在print语句使用,作为标记输出的方便方法。
注意,连续的print语句通常显示在屏幕的不同行上。空print(无参数)生成空行输出。在背后,真正发生的是,在打印所有提供的表达式之后,print函数自动附加某种结束文本。默认情况下,结束文本是表示行结束的特殊标记字符(表示为“\n”)。我们可以通过包含一个附加参数显式地覆盖这个默认值,从而改变这种行为。这里使用命名参数的特殊语法,或称为“关键字”参数。
包含指定结束文本的关键字参数的print语句的模板如下:
print(<expr>, <expr>, ..., <expr>, end="\n")
命名参数的关键字是end,它使用“=”符号赋值,类似于变量赋值。注意,在模板中我已经显示其默认值,即行末字符。这是一种标准方式,用于显示在未明确指定某个其他值时,关键字参数具有的值。
print语句中的end参数有一个常见用法,即允许多个print构建单行输出。例如:
print("The answer is", end=" ")
print(3 + 4)
产生单行输出:
The answer is 7
注意,第一个print语句的输出如何以空格(" ")而不是行末字符结束,第二个语句的输出紧跟在空格之后。
Python中最重要的语句之一是赋值语句。我们在前面的例子中已经看到了一些。
基本赋值语句具有以下形式:
<variable> = <expr>
这里variable是一个标识符,expr是一个表达式。赋值的语义是,右侧的表达式被求值,然后产生的值与左侧命名的变量相关联。
下面是我们已经看到的一些赋值:
x = 3.9 * x * (1 - x)
fahrenheit = 9 / 5 * celsius + 32
x = 5
变量可以多次赋值。它总是保留最新赋的值。下面的交互式Python会话展示了这一点:
>>> myVar = 0
>>> myVar
0
>>> myVar = 7
>>> myVar
7
>>> myVar = myVar + 1
>>> myVar
8
最后一个赋值语句展示了如何使用变量的当前值来更新它的值。在这个例子中,我只是对以前的值加1。第1章的chaos.py程序做了类似的事情,但更复杂一些。记住,变量的值可以改变,这就是为什么它们被称为变量的原因。
有时,将变量看作计算机内存中的一种命名的存储位置是有帮助的,我们可以在其中放入一个值。当变量更改时,旧值将被删除,并写入一个新值。图2.1展示了用这个模型来描绘x = x + 1的效果。这正是赋值在某些计算机语言中工作的方式。这也是查看赋值效果的一种非常简单的方式,你会在整本书中看到类似这样的图片。
图2.1 x = x + 1的视图,变量就像盒子
Python赋值语句实际上与“变量盒子”模型略有不同。在Python中,值可能最终放在内存中的任何位置,而变量用于引用它们。对变量赋值就像把一个黄色小粘贴便签放在值上,并说“这是x”。图2.2给出了一个更准确的Python赋值的效果。箭头用于显示变量引用的值。请注意,旧值不会被新值擦除,变量只需切换到引用新值。效果就像将粘贴便签从一个对象移动到另一个对象一样。这是赋值在Python中实际工作的方式,所以你会看到这样一些粘贴便签样式的图片散布在本书中。
图2.2 x = x + 1的(Python)视图,变量就像便签
顺便说一句,即使赋值语句不直接导致变量的旧值被擦除和覆盖,你也不必担心计算机内存中充满“被丢弃”的值。如果一个值不再被任何变量引用,它就不再有用。Python将自动从内存中清除这些值,以便空间可以用于存放新值。这就像检查你的衣柜,抛出没有粘贴便签标记的东西。实际上,这个自动内存管理的过程确实被称为“垃圾收集”。
输入语句的目的是从程序的用户那里获取一些信息,并存储到变量中。一些编程语言有一个特殊的语句来做到这一点。在Python中,输入是用一个赋值语句结合一个内置函数input实现的。输入语句的确切形式,取决于你希望从用户那里获取的数据类型。对于文本输入,语句如下所示:
<variable> = input(<prompt>)
这里的<prompt>是一个字符串表达式,用于提示用户输入。提示几乎总是一个字符串字面量(即引号内的一些文本)。
当Python遇到对input的调用时,它在屏幕上打印提示。然后,Python暂停并等待用户键入一些文本,键入完成后按<Enter>键。用户输入的任何东西都会存储为字符串。请考虑以下简单的交互:
>>> name = input("Enter your name: ")
Enter your name: John Yaya
>>> name
'John Yaya'
执行input语句导致Python打印输出提示“Enter your name:”,然后解释器暂停,等待用户输入。在这个例子中,我键入John Yaya。结果,字符串“John Yaya”被记在变量name中。对name求值将返回我键入的字符串。
如果用户输入是一个数字,我们需要形式稍复杂一点的input语句:
<variable> = eval(input(<prompt>))
这里我添加了另一个内置的Python函数eval,它“包裹”了input函数。你可能会猜到,eval是“evaluate(求值)”的缩写。在这种形式中,用户键入的文本被求值为一个表达式,以产生存储到变量中的值。举例来说,字符串“32”就变成数字32。如果回头看看示例程序,到目前为止,你会看到几个例子,我们像这样从用户那里得到了数字。
x = eval(input("Please enter a number between 0 and 1: "))
celsius = eval(input("What is the Celsius temperature? "))
重要的是要记住,如果希望得到一个数字,而不是一些原始文本(字符串),需要对input进行eval。
如果你仔细阅读示例程序,可能会注意到所有这些提示结尾处的引号内的空格。我通常在提示的末尾放置一个空格,以便用户输入的内容不会紧接着提示开始。放上空格可以让交互更容易阅读和理解。
虽然我们的数字示例特别提示用户输入数字,但在这个例子中,用户键入的只是一个数字字面量,即一个简单的Python表达式。事实上,任何有效的表达式都是可接受的。请考虑下面与Python解释器的交互:
>>> ans = eval(input("Enter an expression: "))
Enter an expression: 3 + 4 * 5
>>> print(ans)
23
>>>
这里,提示输入表达式时,用户键入“3 + 4 * 5”。Python对此表达式求值(通过eval),并将值赋给变量ans。打印时,我们看到ans的值为23,与预期一样。在某种意义上,input-eval组合就像一个延迟的表达式。示例交互产生完全相同的结果,就像我们简单地写成ans = 3 + 4 * 5一样。不同的是,表达式由用户在语句执行时提供,而不是由程序员在编程时输入。
注意:eval函数功能非常强大,也有“潜在的危险”。如本例所示,当我们对用户输入求值时,本质上是允许用户输入一部分程序。Python将尽职尽责地对他们输入的任何内容求值。了解Python的人可以利用这种能力输入恶意指令。例如,用户可以键入记录计算机上的私人信息或删除文件的表达式。在计算机安全中,这被称为“代码注入”攻击,因为攻击者将恶意代码注入正在运行的程序中。
作为一名新程序员,编程给自己个人使用,计算机安全不是很大的问题。如果你坐在一台运行Python程序的计算机前面,你可能拥有对系统的完全访问权限,并且可以找到更简单的方法来删除所有文件。然而,如果一个程序的输入来自不受信任的来源,例如来自互联网上的用户,使用eval可能是灾难性的。幸运的是,你将在下一章看到一些更安全的替代方法。
有一个赋值语句的替代形式,允许我们同时计算几个值。它看起来像这样:
<var1>, <var2>, ..., <varn> = <expr1>, <expr2>, ..., <exprn>
这称为“同时赋值”。语义上,这告诉Python对右侧所有表达式求值,然后将这些值赋给左侧命名的相应变量。下面是一个例子:
sum, diff = x+y, x-y
这里,sum得到x和y的和,diff得到x和y的差。
这种形式的赋值初看很奇怪,但实际上非常有用。这里有一个例子:假设有两个变量x和y,你希望交换它们的值。也就是说,你希望将当前存储在x中的值存储在y中,将当前存储在y中的值存储在x中。首先,你可能认为这可以通过两个简单的赋值来完成:
x = y
y = x
这不行。我们可以一步一步地跟踪这些语句的执行,看看为什么。
假设x和y开始的值是2和4。让我们检查程序的逻辑,看看变量是如何变化的。以下序列用注释描述了在执行这两个语句时变量会发生什么:
# 变量 x y
# 初始值 2 4
x = y
# 现在是 4 4
y = x
# 最后是 4 4
看到第一个语句将y的值赋给x,从而修改了x的原始值吗?当我们在第二步将x的值赋给y时,最终得到了原始y值的两个副本。
完成交换的一种方法是引入一个附加变量,它暂时记住x的原始值。
temp = x
x = y
y = temp
让我们来看看这个序列是如何工作的。
# 变量 x y temp
# 初始值 2 4 暂时无值
temp = x
# 2 4 2
x = y
# 4 4 2
y = temp
# 4 2 2
从x和y的最终值可以看出,在这个例子中,交换成功。
这种三变量交换的方式在其他编程语言中很常见。在Python中,同时赋值语句提供了一种优雅的选择。下面是更简单的Python等价写法:
x, y = y, x
因为赋值是同时的,所以它避免了擦除一个原始值。
同时赋值也可以用单个input从用户那里获取多个数字。请考虑下面的程序,它求出考试平均分:
# avg2.py
# A simple program to average two exam scores
# Illustrates use of multiple input
def main():
print("This program computes the average of two exam scores.")
score1, score2 = eval(input("Enter two scores separated by a comma: "))
average = (score1 + score2) / 2
print("The average of the scores is:", average)
main()
该程序提示用逗号分隔两个分数。假设用户键入86,92。input语句的效果就像进行以下赋值:
score1, score2 = 86, 92
我们已经为每个变量获得了一个值。这个例子只用了两个值,但可以扩展到任意数量的输入。
当然,我们也可以通过单独的input语句获得用户的输入:
score1 = eval(input("Enter the first score: "))
score2 = eval(input("Enter the second score: "))
某种程度上,这可能更好,因为单独的提示对用户来说信息更准确。在这个例子中,决定采用哪种方法在很大程度上是品位问题。有时在单个input中获取多个值提供了更直观的用户接口,因此在你的工具包中,这是一项好技术。但要记住,多个值的技巧不适用于字符串(非求值)输入,如果用户键入逗号,它只是输入字符串中的一个字符。逗号仅在随后对字符串求值时,才成为分隔符。
你已经知道,程序员用循环连续多次执行一系列语句。最简单的循环称为“确定循环”。这是会执行一定次数的循环。也就是说,在程序中循环开始时,Python就知道循环(或“迭代”)的次数。例如,第1章中的chaos程序用了一个总是执行10次的循环:
for i in range(10):
x = 3.9 * x * (1 - x)
print(x)
这个特定的循环模式称为“计数循环”,它用Python的for语句构建。在详细分析这个例子之前,让我们来看看什么是for循环。
Python的for循环具有以下一般形式:
for <var> in <sequence>:
<body>
循环体可以是任意Python语句序列。循环体的范围通过它在循环头(for <var> in <sequence>:部分)下面的缩进来表示。
关键字for后面的变量称为“循环索引”。它依次取sequence中的每个值,并针对每个值都执行一次循环体中的语句。通常,sequence部分由值“列表”构成。列表是Python中一个非常重要的概念,你将在后续章节中了解更多。现在只要知道,可以在方括号中放置一系列表达式,从而创建一个简单的列表。下列交互示例有助于说明这一点:
>>> for i in [0, 1, 2, 3]:
print(i)
0
1
2
3
>>> for odd in [1, 3, 5, 7, 9]:
print(odd * odd)
1
9
25
49
81
你能看到这两个例子做了什么吗?依次使用列表中的每个值执行了循环体。列表的长度决定了循环执行的次数。在第一个例子中,列表包含4个值,即0至3,并且简单地打印了这些连续的i值。在第二个例子中,odd取前5个奇数的值,循环体打印了这些数字的平方。
现在,让我们回到这一节开始的例子(来自chaos.py)再看一下循环头:
for i in range(10):
将它与for循环的模板进行比较可以看出,最后一个部分range(10)必定是某种序列。事实上,range是一个内置的Python函数,用于“当场”生成一个数字序列。你可以认为range是一种数字序列的隐性描述。要明白range实际上做了什么,我们可以要求Python用另一个内置函数list,将range转换为一个简单的旧式列表:
>>> list(range(10)) # turns range(10) into an explicit list
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
你看到这里发生了什么吗?表达式range(10)产生数字0到9的序列。使用range(10)的循环等价于使用那些数字的列表的循环。
for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]:
一般来说,range(<expr>)将产生一个数字序列,从0开始,但不包括<expr>的值。如果你想一想,就会发现表达式的值确定了结果序列中的项数。在chaos.py中,我们甚至不关心循环索引变量使用了什么值(因为i没有在循环体中的任何位置引用)。我们只需要一个长度为10的序列,让循环体执行10次。
正如前面提到的,这种模式称为“计数循环”,它是使用确定循环的一种很常见的方式。如果你希望在程序中做一定次数的某些事,请用一个带有合适range的for循环。下面一个反复出现的Python编程习语,你需要记住:
for <variable> in range(<expr>):
表达式的值确定了循环执行的次数。索引变量的名称实际上并不重要,程序员经常使用i或j作为计数循环的循环索引变量。只要确保使用的标识符没有用于任何其他目的,否则你可能会不小心清除稍后需要的值。
循环的有趣和有用之处在于,它们改变程序“控制流”的方式。通常我们认为计算机是严格按顺序执行一系列指令。引入循环会导致Python退回去并重复执行一些语句。类似for循环的语句称为“控制结构”,因为它们控制程序其他部分的执行。
一些程序员发现,用图片的方式来思考控制结构是有帮助的,即所谓的“流程图”。流程图用一些框来表示程序的不同部分,并用框之间的箭头表示程序运行时的事件序列。图2.3用流程图描述了for循环的语义。
图2.3 for循环的流程图
如果你在理解for循环时遇到困难,可能会发现学习流程图很有用。流程图中的菱形框表示程序中的决定。当Python遇到循环头时,它检查序列中是否有项。如果答案为“是”,则循环索引变量被赋予序列中的下一项,然后执行循环体。一旦循环体完成,程序返回到循环头并检查序列中的下一个值。如果没有更多的项,循环就退出,程序移动到循环之后的语句。
我们用另一个编程过程的例子来结束本章。我们希望开发一个程序来确定投资的终值。我们将从对问题的分析开始。你知道存入银行账户的钱会赚取利息,这个利息随着时间的推移而累积。从现在起10年后,一个账户将有多少钱?显然,这取决于我们开始有多少钱(本金)以及账户赚多少利息。给定本金和利率,程序应该能够计算未来10年投资的终值。
我们继续制定程序的确切规格说明。记住,这是程序做什么的描述。输入应该是什么?我们需要用户输入初始投资金额,即本金。我们还需要说明账户赚多少利息。这取决于利率和计复利的频率。处理此问题的一种简单方法是让用户输入年度百分比率。无论实际利率和复利频率如何,年利率告诉我们一年内的投资收益。如果年利率为3%,那么100美元的投资将在一年的时间内增长到103美元。用户应如何表示年利率3%?有一些合理的选择。让我们假设用户提供一个小数,因此利率将输入为0.03。
这样就得到以下规格说明:
程序 终值
输入
principal 投资于美元的金额。
APR 以十进制数表示的年度百分比利率。
输出 投资10年后的终值。
关系 一年后的价值由principal(1 + apr)给出。该公式需要应用10次。
接下来为程序设计一个算法。我们将使用伪代码,这样就可以阐明我们的想法而又不必担心Python的所有规则。对于我们的规格说明,算法看起来很简单。
打印介绍
输入本金的金额(principal)
输入年度百分比利率(apr)
重复10次:
principal = principal *(1 + apr)
输出principal的值
如果你知道一些金融数学(或者只是一些基本代数)的知识,可能会意识到,在这个设计中并不一定要用循环。有一个公式可以利用乘幂一步算出终值。我在这里用了一个循环来展示另一个计数循环,另一个原因是这个版本适合进行一些修改,在本章末尾的编程练习中将讨论。无论如何,这个设计说明有时算法的计算方式可以让数学更容易。知道如何计算一年的利息,就让我们能计算未来任意年数的利息。
既然我们已经在伪代码中想明白了这个问题,现在该利用我们的Python新知识开发一个程序了。算法的每一行都转换为一条Python语句:
打印介绍(print语句,第2.4节)
print("This program calculates the future value")
print("of a 10-year investment.")
输入本金的金额(数值input,第2.5.2节)
principal = eval(input("Enter the initial principal: "))
输入年度百分比利率(数值input,第2.5.2节)
apr = eval(input("Enter the annual interest rate: "))
重复10次:(计数循环,第2.6节)
for i in range(10):
计算principal = principal * (1 + apr)(简单赋值,第2.5.1节)
principal = principal * (1 + apr)
输出principal的值(print语句,第2.4节)
print("The value in 10 years is:", principal)
该程序中的所有语句类型都已在本章中详细讨论过。如果有任何问题,请回头查看相关说明。特别要注意的是,计数循环模式用于应用10次利息公式。
就到这里了。下面是完成的程序:
# futval.py
# A program to compute the value of an investment
# carried 10 years into the future
def main():
print("This program calculates the future value")
print("of a 10-year investment.")
principal = eval(input("Enter the initial principal: "))
apr = eval(input("Enter the annual interest rate: "))
for i in range(10):
principal = principal * (1 + apr)
print("The value in 10 years is:", principal)
main()
注意,我添加了几个空行来分隔程序的输入、处理和输出部分。策略性地放置“空行”能让程序更具有可读性。
这就是我所举的例子,测试和调试是留给你的练习。
本章介绍了开发程序的过程,以及实现简单程序所需的许多Python细节。下面是一些要点的快速小结。
1.编写程序的最好方法是立即键入一些代码,然后调试它,直到它工作。
2.可以在不使用编程语言的情况下编写算法。
3.程序在写入和调试后不再需要修改。
4.Python标识符必须以字母或下划线开头。
5.关键词是好的变量名。
6.表达式由文字、变量和运算符构成。
7.在Python中,x = x + 1是一个合法的语句。
8.Python不允许使用单个语句输入多个值。
9.计数循环被设计为迭代特定次数。
10.在流程图中,菱形用于展示语句序列,矩形用于判断点。
1.以下 项不是软件开发过程中的一个步骤。
a.规格说明
b.测试/调试
c.决定费用
d.维护
2.将摄氏度转换为华氏度的正确公式是 。
a. F = 9/5(C) + 32
b.F = 5/9(C) − 32
c. F = B2 − 4AC
d.F = (212 – 32)/(100 – 0)
3.准确描述计算机程序将做什么来解决问题的过程称为 。
a.设计
b.实现
c.编程
d.规格说明
4.以下 项不是合法的标识符。
a.spam
b.spAm
c.2spam
d.spam4U
5.下列 不在表达式中使用。
a.变量
b.语句
c.操作符
d.字面量
6.生成或计算新数据值的代码片段被称为 。
a.标识符
b.表达式
c.生成子句
d.赋值语句
7.以下 项不是IPO模式的一部分。
a.输入
b.程序
c.处理
d.输出
8.模板for <variable> in range(<expr>)
描述了 。
a.一般for
循环
b.赋值语句
c.流程图
d.计数循环
9.以下 项是最准确的Python赋值模型。
a.粘贴便签
b.变量盒子
c.同时
d.塑料尺
10.在Python中,获取用户输入通过一个特殊的表达式来实现,称为 。
a.for
b.read
c.同时赋值
d.input
1.列出并用你自己的语言描述软件开发过程中的六个步骤。
2.写出chaos.py程序(第1.6节),并识别程序的各部分如下:
3.解释确定循环、for循环和计数循环几个概念之间的关系。
4.显示以下片段的输出:
a.
for i in range(5):
print(i * i)
b.
for d in [3,1,4,1,5]:
print(d, end=" ")
c.
for i in range(4):
print("Hello")
d.
for i in range(5):
print(i, 2**i)
5.先写出一个算法的伪代码而不是立即投入Python代码,为什么是一个好主意?
6.除end之外,Python的print
函数还支持其他关键字参数。其中一个关键字参数是sep
。你认为sep
参数是什么?(提示:sep
是分隔符的缩写。通过交互式执行或通过查阅Python文档来检验你的想法)。
7.如果执行下面的代码,你认为会发生什么?
print("start")
for i in range(0):
print("Hello")
print("end")
看看本章的for语句的流程图,帮助你弄明白。然后在程序中尝试这些代码,检验你的预测。
1.一个用户友好的程序应该打印一个介绍,告诉用户程序做什么。修改convert.py程序(第2.2节),打印介绍。
2.在许多使用Python的系统上,可以通过简单地点击(或双击)程序文件的图标来运行程序。如果你能够以这种方式运行convert.py程序,你可能会发现另一个可用性问题。 程序在新窗口中开始运行,但程序一完成,窗口就会消失,因此你无法读取结果。在程序结束时添加一个输入语句,让它暂停,给用户一个读取结果的机会。下面这样的代码应该有效:
input("Press the <Enter> key to quit.")
3.修改avg2.py
程序(第2.5.3节),找出三个考试成绩的平均值。
4.使用循环修改convert.py
程序(第2.2节),让它在退出前执行5次。每次通过循环,程序应该从用户获得另一个温度,并打印转换的值。
5.修改convert.py
程序(第2.2节),让它计算并打印一个摄氏温度和华氏度的对应表,从0℃到100℃,每隔10℃一个值。
6.修改futval.py
程序(第2.7节),让投资的年数也由用户输入。确保更改最后的消息,以反映正确的年数。
7.假设你有一个投资计划,每年投资一定的固定金额。修改futval.py
,计算你的投资的总累积值。该程序的输入将是每年投资的金额、利率和投资的年数。
8.作为APR的替代方案,账户所产生的利息通常通过名义利率和复利期数来描述。例如,如果利率为3%,利息按季度计算复利,则该账户实际上每3个月赚取0.75%的利息。请修改futval.py
程序,用此方法输入利率。程序应提示用户每年的利率(rate)和利息每年复利的次数(periods)。要计算10年的价值,程序将循环10 * periods次,并在每次迭代中累积rate/period的利息。
9.编写一个程序,将温度从华氏温度转换为摄氏温度。
10.编写一个程序,将以千米为单位的距离转换为英里。1千米约为0.62英里。
11.编写一个程序以执行你自己选择的单位转换。确保程序打印介绍,解释它的作用。
12.编写一个交互式Python计算器程序。程序应该允许用户键入数学表达式,然后打印表达式的值。加入循环,以便用户可以执行许多计算(例如,最多100个)。注意:要提前退出,用户可以通过键入一个错误的表达式,或简单地关闭计算器程序运行的窗口,让程序崩溃。在后续章节中,你将学习终止交互式程序的更好方法。
计算机刚开发出来时,它们主要被视为数字处理器,现在这仍然是一个重要的应用。如你所见,涉及数学公式的问题很容易转化为Python程序。在本章中,我们将仔细观察一些程序,它们的目的是执行数值计算。
计算机程序存储和操作的信息通常称为“数据”。不同种类的数据以不同的方式存储和操作。请考虑这个计算零钱的程序:
# change.py
# A program to calculate the value of some change in dollars
def main():
print("Change Counter")
print()
print("Please enter the count of each coin type.")
quarters = eval(input("Quarters: "))
dimes = eval(input("Dimes: "))
nickels = eval(input("Nickels: "))
pennies = eval(input("Pennies: "))
total = quarters * .25 + dimes * .10 + nickels * .05 + pennies * .01
print()
print("The total value of your change is", total)
main()
下面是输出示例:
Change Counter Please enter the count of each coin type.
Quarters: 5
Dimes: 3
Nickels: 4
Pennies: 6
The total value of your change is 1.81
这个程序实际上操作两种不同的数字。用户输入的值(5,3,4,6)是整数,它们没有任何小数部分。硬币的值(.25,.10,.05,.01)是分数的十进制表示。在计算机内部,整数和具有小数部分的数字以不同的方式存储。从技术上讲,这是两种不同的“数据类型”。
对象的数据类型决定了它可以具有的值以及可以对它执行的操作。整数用“integer”数据类型(简写为“int”)表示。int类型的值可以是正数或负数。可以具有小数部分的数字表示为“floating-point(浮点)”(或“float”)值。那么我们如何判断一个数值是int还是float呢?不包含小数点的数值字面量生成一个int值,但是具有小数点的字面量由float表示(即使小数部分为0)。
Python提供了一个特殊函数,名为type,它告诉我们任何值的数据类型(或“class”)。下面是与Python解释器的交互,显示int和float字面量之间的区别:
>>> type(3)
<class 'int'>
>>> type(3.14)
<class 'float'>
>>> type(3.0)
<class 'float'>
>>> myInt = -32
>>> type(myInt)
<class 'int'>
>>> myFloat = 32.0
>>> type(myFloat)
<class 'float'>
你可能希望知道,为什么有两种不同的数据类型。一个原因涉及程序风格。表示计数的值不能为小数,例如,我们不能有3.12个季度。使用int值告诉读者程序的值不能是一个分数。另一个原因涉及各种操作的效率。对于int,执行计算机运算的基础算法更简单,因此可以更快,而float值所需的算法更通用。当然,在现代处理器上,浮点运算的硬件实现是高度优化的,可能与int运算一样快。
int和float之间的另一个区别是,float类型只能表示对实数的近似。我们会看到,存储值的精度(或准确度)存在限制。由于浮点值不精确,而int总是精确的,所以一般的经验法则应该是:如果不需要小数值,就用int。
值的数据类型决定了可以使用的操作。如你所见,Python支持对数值的一般数学运算。表3.1总结了这些操作。实际上,这个表有些误导。由于这两种类型具有不同的底层表示,所以它们各自具有不同的一组操作。例如,我只列出了一个加法操作,但请记住,对float值执行加法时,计算机硬件执行浮点加法,而对int值,计算机执行整数加法。Python基于操作数选择合适的底层操作(int或float)。
表3.1 Python内置的数值操作
操作符 |
操作 |
---|---|
+ |
加 |
- |
减 |
* |
乘 |
/ |
浮点除 |
** |
指数 |
abs() |
绝对值 |
// |
整数除 |
% |
取余 |
请考虑以下Python交互:
>>> 3 + 4
7
>>> 3.0 + 4.0
7.0
>>> 3 * 4
12
>>> 3.0 * 4.0
12.0
>>> 4 ** 3
64
>>> 4.0 ** 3
64.0
>>> 4.0 ** 3.0
64.0
>>> abs(5)
5
>>> abs(-3.5)
3.5
>>>
在大多数情况下,对float的操作产生float,对int的操作产生int。大多数时候,我们甚至不必担心正在执行什么类型的操作。例如,整数加法与浮点加法产生的结果几乎相同,我们可以相信Python会做正确的事情。
然而,在除法时,事情就比较有趣了。如表所列,Python(版本3.0)提供了两种不同的运算符。通常的符号(/)用于“常规”除法,双斜线(//)用于表示整数除法。找到它们之间差异的最佳方法就是试一下。
>>> 10 / 3
3.3333333333333335
>>> 10.0 / 3.0
3.3333333333333335
>>> 10 / 5
2.0
>>> 10 // 3
3
>>> 10.0 // 3.0
3.0
>>> 10 % 3
1
>>> 10.0 % 3.0
1.0
请注意,“/”操作符总是返回一个浮点数。常规除法通常产生分数结果,即使操作数可能是int。Python通过返回一个浮点数来满足这个要求。10/3的结果最后有一个5,你是否感到惊讶?请记住,浮点值总是近似值。该值与Python将表示为浮点数时得到的近似值相同。
要获得返回整数结果的除法,可以使用整数除法运算“//”。整数除法总是产生一个整数。 把整数除法看作gozinta(进入或整除)。表达式10 // 3得到3,因为3 进入 10共计3次(余数为1)。虽然整数除法的结果总是一个整数,但结果的数据类型取决于操作数的数据类型。浮点整数整除浮点数得到一个浮点数,它的分数分量为0。最后两个交互展示了余数运算%。请再次注意,结果的数据类型取决于操作数的类型。
由于数学背景不同,你可能没用过整数除法或余数运算。要记住的是,这两个操作是密切相关的。整数除法告诉你一个数字进入另一个数字的次数,剩余部分告诉你剩下多少。数学上你可以写为a = (a//b)(b) + (a%b)。
作为示例应用程序,假设我们以美分来计算零钱(而不是美元)。如果我有383美分,那么我可以通过计算383 // 100 = 3找到完整美元的数量,剩余的零钱是383%100 = 83。因此,我肯定共有3美元和83美分的零钱。
顺便说一句,虽然Python(版本3.0)将常规除法和整数除法作为两个独立的运算符,但是许多其他计算机语言(和早期的Python版本)只是使用“/”来表示这两种情况。当操作数是整数时,“/”表示整数除法,当它们是浮点数时,它表示常规除法。这是一个常见的错误来源。例如,在我们的温度转换程序中,公式9/5 * celsius + 32不会计算正确的结果,因为9/5将使用整数除法计算为1。在这些语言中,你需要小心地将此表达式编写为9.0 / 5.0 * celsius + 32,以便使用正确的除法形式,从而得到分数结果。
在某些情况下,值可能需要从一种数据类型转换为另一种数据类型。你已知道,int和int组合(通常)产生一个int,float和float组合创建另一个float。但是如果我们写一个混合int和float的表达式会发生什么呢?例如,在下列赋值语句之后,x的值应该是什么:
x = 5.0 * 2
如果这是浮点乘法,则结果应为浮点值10.0。如果执行整型乘法,结果就是10。在继续读下去获得答案之前,请花一点时间考虑:你认为Python应该怎样处理这种情况。
为了理解表达式5.0 * 2,Python必须将5.0转换为5并执行int操作,或将2转换为2.0并执行浮点操作。一般来说,将float转换为int是一个危险的步骤,因为一些信息(小数部分)会丢失。另一方面,int可以安全地转换为浮点,只需添加一个小数部分0。因此,在“混合类型表达式”中,Python会自动将int转换为浮点数,并执行浮点运算以产生浮点数结果。
有时我们可能希望自己执行类型转换。这称为显式类型转换。Python为这些场合提供了内置函数int和float。以下一些交互示例说明了它们的行为:
>>> int(4.5)
4
>>> int(3.9)
3
>>> float(4)
4.0
>>> float(4.5)
4.5
>>> float(int(3.3))
3.0
>>> int(float(3.3))
3
>>> int(float(3))
3
如你所见,转换为int就是丢弃浮点值的小数部分,该值将被截断,而不是舍入。如果你希望一个四舍五入的结果,假设值为正,可以在使用int()之前加上0.5。对数字进行四舍五入的更一般方法是使用内置的round函数,它将数字四舍五入到最接近的整数值。
>>> round(3.14)
3
>>> round(3.5)
4
请注意,像这样调用round会产生一个int值。因此,对round的简单调用是将float转换为int的另一种方法。
如果要将浮点值舍入为另一个浮点值,则可以通过提供第二个参数来指定在小数点后的数字位数。下面的交互处理π的值:
>>> pi = 3.141592653589793
>>> round(pi, 2)
3.14
>>> round(pi,3)
3.142
请注意,当我们将π近似舍入到两位或三位小数时,我们得到一个浮点数,其显示值看起来像一个完全舍入的结果。记住,浮点值是近似。真正得到的是一个非常接近我们要求的值。实际存储的值类似于3.140000000000000124345……,最接近的可表示的浮点值为3.14。幸运的是,Python是聪明的,知道我们可能不希望看到所有这些数字,所以它显示了舍入的形式。这意味着如果你编写一个程序,将一个值四舍五入到两位小数,并打印出来,就会看到两位小数,与你期望的一样。在第5章中,我们将看到如何更好地控制打印数字的显示方式,那时如果你希望,就能查看所有的数字。
类型转换函数int和float也可以用于将数字字符串转换为数字。
>>> int("32")
32
>>> float("32")
32.0
>>> float("9.8")
9.8
作为替代eval从用户获取数字数据的另一种方法,这特别有用。例如,下面是本章开始时零钱计数程序的一个改进版本:
# change2.py
# A program to calculate the value of some change in dollars
def main():
print("Change Counter")
print()
print("Please enter the count of each coin type.")
quarters = int(input("Quarters: "))
dimes = int(input("Dimes: "))
nickels = int(input("Nickels: "))
pennies = int(input("Pennies: "))
total = .25*quarters + .10*dimes + .05*nickels + .01*pennies
print()
print("The total value of your change is", total)
main()
在input语句中使用int而不是eval,可以确保用户只能输入有效的整数。任何非法(非int)输入将导致程序崩溃和错误消息,从而避免代码注入攻击的风险(在第2.5.2节讨论)。另一个好处是,这个版本的程序强调输入应该是整数。
使用数字类型转换代替eval的唯一缺点是,它不支持同时输入(在单个输入中获取多个值),如下例所示:
>>> # simultaneous input using eval
>>> x,y = eval(input("Enter (x,y): "))
Enter (x,y): 3,4
>>> x
3
>>> y
4
>>> # does not work with float
>>> x,y = float(input("Enter (x,y): "))
Enter (x,y): 3,4
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: could not convert string to float: '3,4'
这个代价很小,换来了额外的安全性。在第5章,你将学习如何克服这个限制。作为一种良好的实践,你应该尽可能使用适当的类型转换函数代替eval。
除表3.1中列出的操作之外,Python还在一个特殊的math“库”中提供了许多其他有用的数学函数。库就是一个模块,包含了一些有用定义。我们的下一个程序展示了使用这个库来计算二次方程的根。
二次方程的形式为ax2 + bx + c = 0。这样的方程有两个解,由求根公式给出:
让我们编写一个程序,找到二次方程的解。程序的输入将是系数a、b和c的值,输出是由求根公式给出的两个值。下面是完成这项工作的程序:
# quadratic.py
# A program that computes the real roots of a quadratic equation.
# Illustrates use of the math library.
# Note: This program crashes if the equation has no real roots.
import math # Makes the math library available.
def main():
print("This program finds the real solutions to a quadratic")
print()
a = float(input("Enter coefficient a: "))
b = float(input("Enter coefficient b: "))
c = float(input("Enter coefficient c: "))
discRoot = math.sqrt(b * b - 4 * a * c)
root1 = (-b + discRoot) / (2 * a)
root2 = (-b - discRoot) / (2 * a)
print()
print("The solutions are:", root1, root2 )
main()
该程序使用了math库模块的平方根函数sqrt。在程序的顶部,import math告诉Python我们正在使用math模块。导入模块让程序中定义的任何内容都可用。要计算,我们使用math.sqrt(x)。这个特殊的点符号告诉Python,使用“生存”在math模块中的sqrt函数。在二次方程程序中,我们用下面的代码行来计算
:
discRoot = math.sqrt(b * b - 4 * a * c)
下面是程序运行的情况:
This program finds the real solutions to a quadratic
Enter coefficient a: 3
Enter coefficient b: 4
Enter coefficient c: -2
The solutions are: 0.38742588672279316 -1.7207592200561266
只要我们要解的二次方程有实数解,这个程序就很好。但是,一些输入会导致程序崩溃。下面是另一个运行示例:
This program finds the real solutions to a quadratic
Enter coefficient a: 1
Enter coefficient b: 2
Enter coefficient c: 3
Traceback (most recent call last):
File "quadratic.py", line 21, in ?
main()
File "quadratic.py", line 14, in main
discRoot = math.sqrt(b * b - 4 * a * c)
ValueError: math domain error
这里的问题是b2 − 4ac < 0,sqrt函数无法计算负数的平方根。Python打印“math domain error”。这告诉我们,负数不在sqrt函数的定义域中。现在,我们没有工具来解决这个问题,所以我们只需要假设用户会给我们可解的方程。
实际上,quadratic.py不需要使用math库。我们可以用乘方**来取平方根。(你知道怎么做吗?)使用math.sqrt更高效一些,而且它让我展示使用math库。一般来说,如果你的程序需要一个通用的数学函数,首先要看看math库。表3.2显示了math库中提供的一些其他函数。
表3.2 一些math库函数
Python |
数学 |
描述 |
---|---|---|
pi |
π |
π的近似值 |
e |
e |
e的近似值 |
sqrt(x) |
|
x的平方根 |
sin(x) |
sin x |
x的正弦 |
cos(x) |
cos x |
x的余弦 |
tan(x) |
tan x |
x的正切 |
asin(x) |
arcsin x |
x的反正弦 |
acos(x) |
arcos x |
x的反余弦 |
atan(x) |
arctan x |
x的反正切 |
log(x) |
ln x |
x的自然对数(以e为底) |
log10(x) |
Log10 x |
x的常用对数(以10为底) |
exp(x) |
ex |
e的x次方 |
ceil(x) |
[x] |
最小的>=x的整数 |
floor(x) |
[x] |
最大的<=x的整数 |
假设你有一个根汁饮料样品包,含有6种不同的根汁饮料。以不同的顺序喝各种口味可能会影响它们的味道。如果你希望尝试一切可能,有多少不同的顺序?结果答案是一个大得惊人的数字,720。你知道这个数字怎么来的吗?720是6的“阶乘”。
在数学中,阶乘通常用感叹号(!)表示,整数n的阶乘定义为n! = n(n – 1)(n – 2)……(1)。这恰好是n项的不同排列的数量。给定6个项,我们计算6! = (6)(5)(4)(3)(2)(1) = 720种可能的排列。
让我们编写一个程序,来计算用户输入数字的阶乘。程序的基本结构遵循“输入、处理、输出”模式:
输入要计算阶乘的数,n
计算n的阶乘,fact
输出fact
显然,这里棘手的是第二步。
实际如何计算阶乘?让我们手工尝试一下,以便得到处理的思路。在计算6的阶乘时,我们首先计算6(5) = 30。然后我们取该结果并做另一个乘法:30(4) = 120。这个结果乘以3:120(3) = 360。这个结果乘以2:360(2) = 720。根据定义,最后我们将这个结果乘以1,但不会改变最终值720。
现在让我们考虑更一般的算法。这里实际上发生了什么?我们正在做重复乘法,在做的过程中,我们记下得到的乘积。这是一种非常常见的算法模式,称为“累积器”。我们一步一步得到(或累积)最终的值。为了在程序中实现这一点,我们使用“累积器变量”和循环结构。一般模式如下:
初始化累积器变量
循环直到得到最终结果
更新累积器变量的值
意识到这是解决阶乘问题的模式,我们只需要填写细节。我们将累积阶乘。让我们把它保存在一个名为fact的变量中。每次通过循环,我们需要用fact乘以因子序列n、(n – 1)、……、1中的一个。看起来我们应该用一个for循环,迭代这个因子序列。例如,要计算6的阶乘,我们需要像这样工作的循环:
fact = 1
for factor in [6,5,4,3,2,1]:
fact = fact * factor
请花一分钟跟踪这个循环的执行,并说服自己它有效。当循环体首次执行时,fact的值为1,因子为6。因此,fact的新值为1 * 6 = 6。下一次通过循环,因子将为5,fact更新为6 * 5 = 30。该模式对后续每个因子继续,直到累积得到最终结果720。
循环之前对fact赋初始值1,是循环开始所必需的。每次通过循环体(包括第一个),fact的当前值用于计算下一个值。初始化确保fact在第一次迭代时有一个值。每次使用累积器模式时,应确保包含正确的初始化。忘记这一点是新程序员的一个常见错误。
当然,我们还有很多其他的方法来编写这个循环。正如你从数学课上了解到的,乘法是可交换和结合的,所以执行乘法的顺序并不重要。我们可以很容易地走另一个方向。你可能还会注意到,在因子列表中包含1是不必要的,因为乘以1不会更改结果。下面是另一个版本,计算出相同的结果:
fact = 1
for factor in [2,3,4,5,6]:
fact = fact * factor
不幸的是,这两个循环都不能解决原来的问题。我们手工编码了因子列表来计算6的阶乘。我们真正希望的是,一个可以计算任何给定输入n的阶乘的程序。我们需要某种方法,从n的值生成适当的因子序列。
好在用Python的range函数,这很容易做到。回想一下,range(n)产生一个数字序列,从0开始,增长到n,但不包括n。range有一些其他调用方式,可用于产生不同的序列。利用两个参数,range(start,n)产生一个以值start开始的序列,增长到n,但不包括n。第三个版本的range(start,n,step)类似于双参数版本,但它使用step作为数字之间的增量。下面有一些例子:
>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(range(5,10))
[5, 6, 7, 8, 9]
>>> list(range(5, 10, 3))
[5, 8]
给定输入值n,我们有几种不同的range命令,能产生适当的因子列表,用于计算n的阶乘。为了从最小到最大生成它们(我们的第二种循环),我们可以使用range(2,n + 1)。注意,我使用n + 1作为第二个参数,因为范围将上升到n + 1,但不包括此值。我们需要加1来确保n本身被包括,作为最后一个因子。
另一种可能是使用三参数版本的range和负数步长,产生另一个方向(我们的第一种循环)的因子,导致倒计数:range(n,1,−1)。这个循环产生一个列表,从n开始并向下计数(step为−1)到1,但不包括1。
下面是一种可能的阶乘程序版本:
# factorial.py
# Program to compute the factorial of a number
# Illustrates for loop with an accumulator
def main():
n = int(input("Please enter a whole number: "))
fact = 1
for factor in range(n,1,-1):
fact = fact * factor
print("The factorial of", n, "is", fact)
main()
当然,写这个程序还有很多其他方法。我已经提到改变因子的顺序。另一种可能是将fact初始化为n,然后使用从n−1开始的因子(只要n> 0)。你可以尝试这样一些变化,看看你最喜欢哪一个。
有时我们会联想到,用“!”表示阶乘是因为该函数增长非常快。例如,下面是用我们的程序求100的阶乘:
Please enter a whole number: 100
The factorial of 100 is 9332621544394415268169923885626670049071596826
43816214685929638952175999932299156089414639761565182862536979208272237
58251185210916864000000000000000000000000
这是一个相当大的数字!
尽管最新版本的Python对此计算没有困难,但是旧版本的Python(以及其他语言的现代版本,例如C ++和Java)不会如此。例如,下面是使用Java编写的类似程序的几次运行:
# run 1
Please enter a whole number: 6
The factorial is: 720
# run 2
Please enter a whole number: 12
The factorial is: 479001600
# run 3
Please enter a whole number: 13
The factorial is: 1932053504
这看起来不错。我们知道6! = 720。快速检查也确认12! = 479001600。遗憾的是,事实证明,13! = 6227020800。看起来Java程序给出了不正确的答案!
这里发生了什么?到目前为止,我们已经讨论了数值数据类型作为熟悉数字的表示,例如整数和小数(分数)。然而,重要的是要记住,数字的计算机表示(实际数据类型)并不总是表现得像它们所代表的数字那样。
在第1章中你了解到,计算机的CPU可以执行非常基本的操作,如两个数字相加或相乘,还记得吗?更准确地说,CPU可以对计算机的数字的内部表示执行基本操作。这个Java程序的问题是它使用计算机的底层int数据类型来表示整数,并依赖于计算机对int的乘法运算。不幸的是,这些机器int不完全像数学整数。有无穷多个整数,但int的范围是有限的。在计算机内部,int以固定大小的二进制表示存储。为了理解这一切,我们需要了解硬件层面发生了什么。
计算机存储器由电“开关”组成,每个开关可以处于两种可能状态之一,即开或关。每个开关表示一个二进制数字的信息,称为“位”。一位可以编码两种可能性,通常用数字0(关闭)和1(打开)表示。位序列可以用于表示更多的可能性。用两位,我们可以表示四件事:
bit 2 bit 1
0 0
0 1
1 0
1 1
三位允许我们通过对四个两位模式中的每一个添加0或1,来表示八个不同的值:
bit 3 bit 2 bit 1
0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1
你可以看到这里的模式。每增加一位让不同模式的数量加倍。通常,n位可以表示2n个不同的值。
特定计算机用来表示int的位数取决于CPU的设计。现在,典型的PC使用32或64位。对于32位CPU,这意味着有232个可能的值。这些值以0为中心,表示正整数和负整数的范围。现在232/2 = 231。因此,可以在32位int值中表示的整数范围是−231到231 − 1。在上限−1的原因是考虑在范围的上半部分中的0的表示。
有了这个知识,让我们试着理解Java阶乘例子中发生的事情。如果Java程序依赖于32位int表示,它可以存储的最大数字是多少?Python可以给我们一个快速的答案:
>>> 2**31-1
2147483647
注意,这个值(约21亿)在12!(约4.8亿)和13!(约62亿)之间。这意味着Java程序可以很好地计算到12的阶乘,但是之后,表示“溢出”,结果是垃圾。现在你知道了为什么简单的Java程序不能计算13!。当然,这给我们留下了另一个谜题。为什么现代Python程序似乎能很好地用大整数计算?
首先,你可能认为Python使用浮点数据类型来绕过int的大小限制。然而,事实证明,浮点数并没有真正解决这个问题。下面是使用浮点数的修改后的阶乘程序的示例运行:
Please enter a whole number: 30
The factorial of 30 is 2.6525285981219103e+32
虽然这个程序运行很好,但切换到浮点数后,我们不再能得到确切的答案。
非常大(或非常小)的浮点值使用“指数”的方式打印,称为“科学记数法”。结束时的e + 32表示结果等于2.6525285981219103 * 1032。你可以把+32作为一个标记,表示小数点的位置。在这个例子中,它必须向右移动32个位置以获取实际值。但是,小数点右边只有16位数字,因此我们已经“丢失”了最后16位数字。
使用浮点数,我们可以表示比32位int更大的“范围”的值,但“精度”仍然是固定的。事实上,计算机将浮点数保存为一对固定长度(二进制)整数。一个整数称为“尾数”,表示值中的数字串,第二个称为“指数”,记录整数部分结束和小数部分开始的位置(“二进制小数点”在哪里)。回忆一下,我告诉过你浮点是近似值。现在你可以看到原因。因为底层数字是二进制的,所以只有涉及2的幂的分数可以被精确地表示。任何其他分数产生无限重复的尾数。(就像1/3产生无限重复的十进制,因为3不是10的幂)。当无限长的尾数被截断到固定长度以进行存储时,结果是近似的。用于尾数的位数决定了近似值的精确程度,但绕不过它们是近似的事实。
幸运的是,Python对于大的、精确的值有一个更好的解决方案。Python的int不是固定的大小,而是扩展到适应任何值。唯一的限制是计算机可用的内存量。当值很小时,Python就用计算机的底层int表示和操作。当值变大时,Python会自动转换为使用更多位的表示。当然,为了对更大的数字执行操作,Python必须将操作分解为计算机硬件能够处理的更小的单元,类似于你手工计算长除法的方式。这些操作不会那么有效(它们需要更多的步骤),但是它们允许Python的int增长到任意大小。这就是为什么我们的简单阶乘程序可以计算一些大的结果。这是一个非常酷的Python特性。
本章介绍了一些有关进行数值计算的程序的重要细节。下面是一些关键概念的快速摘要。
1.由计算机存储和操作的信息称为数据。
2.由于浮点数是非常准确的,所以通常应该使用它们,而不是int
。
3.像加法和减法这样的操作在math
库中定义。
4.n项的可能排列的数目等于n!。
5.sqrt
函数计算数字的喷射(squirt)。
6.float
数据类型与实数的数学概念相同。
7.计算机使用二进制表示数字。
8.硬件float
可以表示比硬件int
更大范围的值。
9.在获取数字作为用户输入时,类型转换函数(如float
)是eval
的安全替代。
10.在Python中,4 + 5产生与4.0 + 5.0相同的结果类型。
1.下列 项不是内置的Python数据类型。
a.int
b.float
c.rational
d.string
2.以下 项不是内置操作。
a.+
b.%
c.abs()
d.sqrt()
3.为了使用math库中的函数,程序必须包括 。
a.注释
b.循环
c.操作符
d.import语句
4.4!的值是 。
a.9
b.24
c.41
d.120
5.用于存储π的值,最合适的数据类型是 。
a.int
b.float
c.irrational
d.string
6.可以使用5位比特表示的不同值的数量是 。
a.5
b.10
c.32
d.50
7.在包含int和float的混合类型表达式中,Python会进行的转换是 。
a.浮点数到整数
b.整数到字符串
c.浮点数和整数到字符串
d.整数到浮点数
8.下列 项不是Python类型转换函数。
a.float
b.round
c.int
d.abs
9.用于计算阶乘的模式是 。
a.累积器
b.输入、处理、输出
c.计数循环
d.格子
10.在现代Python中,int值大于底层硬件int时,会 。
a.导致溢出
b.转换为float
c.打破计算机
d.使用更多的内存
1.显示每个表达式求值的结果。确保该值以正确的形式表示其类型(int或float)。如果表达式是非法的,请解释为什么。
a.4.0 / 10.0 + 3.5 * 2
b.10 % 4 + 6 / 2
b.abs(4 - 20 // 3) ** 3
d.sqrt(4.5 - 5.0) + 7 * 3
e.3 * 10 // 3 + 10 % 3
f.3 ** 3
2.将以下每个数学表达式转换为等效的Python表达式。你可以假定math库已导入(通过import math)。
a.(3 + 4)(5)
c.4πr2
d.
e.
3.显示将由以下每个range表达式生成的数字序列。
a.range(5)
b.range(3, 10)
c.range(4, 13, 3)
d.range(15, 5, -2)
e.range(5, 3)
4.显示以下每个程序片段产生的输出。
a.
for i in range(1, 11):
print(i*i)
b.
for i in [1,3,5,7,9]:
print(i, ":", i**3)
print(i)
c.
x = 2
y = 10
for j in range(0, y, x):
print(j, end="")
print(x + y)
print("done")
d.
ans = 0
for i in range(1, 11):
ans = ans + i*i
print(i)
print (ans)
5.如果使用负数作为round函数中的第二个参数,你认为会发生什么?例如,round(314.159265,−1)的结果应该是什么?请解释答案的理由。在你写下答案后,请参阅Python文档或尝试一些例子,看看Python在这种情况下实际上做了什么。
6.当整数除法或余数运算的操作数为负数时,你认为会发生什么?考虑以下每种情况并尝试预测结果。然后在Python中试试。(提示:回顾一下神奇的公式a = (a//b)(b) + (a%b)。)
a.−10 // 3
b.−10 % 3
c.10 // −3
d.10 % −3
e.−10 // −3
1.编写一个程序,利用球体的半径作为输入,计算体积和表面积。以下是一些可能有用的公式:
V = 4/3πr3
A = 4πr2
2.给定圆形比萨饼的直径和价格,编写一个程序,计算每平方英寸的成本。面积公式为A = πr2。
3.编写一个程序,该程序基于分子中的氢、碳和氧原子的数量计算碳水化合物的分子量(以克/摩尔计)。程序应提示用户输入氢原子的数量、碳原子的数量和氧原子的数量。然后程序基于这些单独的原子量打印所有原子的总组合分子量。
原子 |
质量(克/摩尔) |
---|---|
H |
1.00794 |
C |
12.0107 |
O |
15.9994 |
例如,水(H2O)的分子量为2(1.00794)+ 15.9994 = 18.01528。
4.编写一个程序,根据闪光和雷声之间的时间差来确定雷击的距离。声速约为1100英尺/秒,1英里为5280英尺。
5.Konditorei咖啡店售卖咖啡,每磅10.50美元加上运费。每份订单的运费为每磅0.86美元 +固定成本1.50美元。编写计算订单费用的程序。
6.使用坐标(x1,y1)和(x2,y2)指定平面中的两个点。编写一个程序,计算通过用户输入的两个(非垂直)点的直线的斜率。
斜率 =
7.编写一个程序,接受两点(见上一个问题),并确定它们之间的距离。
距离 =
8.格里高利闰余是从1月1日到前一个新月的天数。此值用于确定复活节的日期。它由下列公式计算(使用整型算术):
C = year//100
epact = (8 + (C//4) - C + ((8C + 13)//25) + 11(year%19))%30
编写程序,提示用户输入4位数年份,然后输出闰余的值。
9.使用以下公式编写程序以计算三角形的面积,其三边的长度为a、b和c:
10.编写程序,确定梯子斜靠在房子上时,达到给定高度所需的长度。梯子的高度和角度作为输入。计算长度使用公式为:
注意:角度必须以弧度表示。提示输入以度为单位的角度,并使用以下公式进行转换:
11.编程计算前n个自然数的和,其中n的值由用户提供。
12.编程计算前n个自然数的立方和,其中n的值由用户提供。
13.编程对用户输入的一系列数字求和。 程序应该首先提示用户有多少数字要求和,然后依次提示用户输入每个数字,并在输入所有数字后打印出总和。(提示:在循环体中使用输入语句。)
14.编程计算用户输入的一系列数字的平均值。与前面的问题一样,程序会首先询问用户有多少个数字。注意:平均值应该始终为float,即使用户输入都是int。
15.编写程序,通过对这个级数的项进行求和来求近似的π值:4/1 – 4/3 + 4/5 – 4/7 + 4/9 − 4/11 +……程序应该提示用户输入n,要求和的项数,然后输出该级数的前n个项的和。让你的程序从math.pi的值中减去近似值,看看它的准确性。
16.斐波那契序列是数字序列,其中每个连续数字是前两个数字的和。经典的斐波那契序列开始于1,1,2,3,5,8,13,……。编写计算第n个斐波纳契数的程序,其中n是用户输入的值。例如,如果n = 6,则结果为8。
17.你已经看到math库包含了一个计算数字平方根的函数。在本练习中,你将编写自己的算法来计算平方根。解决这个问题的一种方法是使用猜测和检查。你首先猜测平方根可能是什么,然后看看你的猜测是多么接近。你可以使用此信息进行另一个猜测,并继续猜测,直到找到平方根(或其近似)。一个特别好的猜测方法是使用牛顿法。假设x是我们希望的根,guess是当前猜测的答案。猜测可以通过使用计算下一个猜测来改进:
编程实现牛顿方法。程序应提示用户找到值的平方根(x)和改进猜测的次数。从猜测值x / 2开始,你的程序应该循环指定的次数,应用牛顿的方法,并报告猜测的最终值。你还应该从math.sqrt(x)的值中减去你的估计值,以显示它的接近程度。