每天10分钟,带你一周精通Python:第9章数学和BODMAS计算法则
恭喜啊!你现在已经学习到了足够的Python基础知识(和常用的编程知识),可以开始编写我们第一个完整的程序了。在本章中,我们将会编写一个程序来测试我们对算数计算中的BODMAS计算法则(BODMAS是Bracbets of Division,Multiplication, Addition and Subtraction的缩写,即先算程序后算加减,先算括号里的, 再算括号外面的计算法则)的理解。如果你不知道 BODMAS计算法则是什么, 你可以去这个网站上了解一下http://www.mathsisfun. com/operation-order-bodmas.html。
我们的程序将会随机给我们设置一个算数题要我们回答。如果我们输入了错误的答案,程序将会显示正确的答案,并问我们是否还想要尝试回答一个的问题。如果我们答对了,程序将会恭喜我们,并问我们是否还想要回答一个新问题。另外,程序将会一直记录我们的得分,并把得分保存在外部的一个文本文件中。在每个问题后面,我们可以按“-1”键来终止程序。
我已经把这个程序分成了几个小的练习题,你可以尝试自己编写程序。在参考答案之前,你先尝试做这些练习题。答案在附录E中,Python语法的学习虽然简单,但很枯燥。解决问题的过程才是乐趣所在。如果在做这些练习题时遇到了困难,多多努力即可。这样得到的回报才是最大的。
准备好了吗?那我们就开始吧!
第一部分:myPythonFunction.py
下面将为我们的程序编写两个文件。第一个文件是myPythonFunction.py,第二个文件是mathGame.py。第一部分主要编写myPythonFunction.py的代码。
开始前,我们先创建文件myPythonFunction.py。我们在这个文件中将会定义 三个函数。
练习题1:引入模块
我们需要在myPythonFunction.py中引入两个模块:random模块和os模块。
我们将使用random模块中的randint()函数。randint()函数会在我们提供的范围内生成一个随机的整数。我们将使用它为后面的问题生成数字。
从os模块中,我们将会使用remove()和rename ()函数。
尝试引入这两个模块。
练习题2:获取用户的得分
下面我们将要定义我们的第一个函数。我们把它叫作getUserScore()。这个函数接收一个参数,userName。接下来它会以r模式打开文件“userScores.txt”。
userScores.txt看起来和下面的文字类似。
Ann, 100
Benny, 102
Carol,214
Darren, 129
其中每行记录了一个用户的信息。第一个值是该用户的用户名,第二个是用户的得分。
接下来,函数使用for循环一行一行地读取文件。接着每一行都会使用split()函数来分割(参考附录A中的使用split()函数的例子)。
下面,函数会把用户名作为参数传入函数,并检查每行中是否有相同的用户名。如果存在相同的用户名,函数会关闭文件,并返回用户名对应的得分。如果不存在,函数会关闭文件,并返回字符串“-1”。
到目前为止都清楚了吗?尝试开始编写这个函数的代码吧。
完成了吗?
现在需要对我们的代码做些修改。我们在前面打开文件时,使用的是r模式。这么做可以防止任何对文件的偶然写入。然而,当以r模式打开文件时,如果文件不存在,会出现一个IOError错误。因此当我们第一次运行程序时,我们会遇到一个错误,因为文件userScores.txt这个时候还不存在。为了防止这个错误出现,我们可以从下面的两种方法中任选其一。
除了以r模式打开文件,我们可以以w模式打开文件。当以w模式打开时,如果文件预先不存在的话,这个文件会先被创建。这个方法的危险之处在于我们可能会偶尔写入文件,这会导致原有的所有内容被删除。然而,因为我们的用序是个小程序,我们可以仔细检查程序,防止任何偶然写入的发生。
第二种方法是使用一个try,except语句来处理IOError,为了这么做,我们需理把前面的所有代码都放入到try代码块中,然后使用except IOError来处理File not found错误。在except代码块中,我们将会通知用户没有找到文件,然后创建这不文件。我们将使用w模式的open()函数来创建这个文件。这里的不同之处在手我们仅在文件不存在时使用w模式。因为文件初始并不存在,也就没有删除原有内容的危险。在创建文件后,关闭文件并返回字符串“-1”。
你可以选择上面两种方法之一来完成本次的练习题。提供的答案使用的是 第二种方法。一旦你完成之后,我们就可以接着做练习题3了。
练习题3:更新用户的得分
在本练习题中,我们将定义另一个叫作updateUserPoints()的函数,它接收三个参数:uewUser,userName和score。
newUser要么是True,要么是False。如果newUser是True,函数会以添加的模式打开文件userScores.txt,当用户从游戏中退出时,会向文件中添加用户的userName和score。
如果newUser是False,函数将会在文件中更新用户的得分。然而,在Python(或者相关问题的大多数编程语言)中并没有函数让我们更新文本文件。我们仅可以往文件中写或者添加,而不能更新它。
因此,我们需要创建一个临时文件。这在编程中是一个很常见的实践方法。让我们把这个临时文件叫作userScores.tmp,并以w模式打开它。现在,我们需要在userScore.txt文件中循环,并把其中的数据逐行复制到userScores.tmp中。然而,在复制之前,我们会检查这一行的userName是否和参数中提供的用户名一致。如果一致,我们再把得分写回到临时文件之前,会把得分替换为新的得分。
比如,如果提供给函数的参数是False、Benny和158,即updateUserPoints(False.‘Benny’,‘158’),下面的表格展示了原始文件userScores.txt和新文件userScores tmp之间的不同。
userScores.txt
Ann, 100
Benny,102
carol,214
Darren, 129
userScores.tmp
Ann,100
Benny,158
Carol,214
Darren, 129
在完成写入userScores.tmp后,我们将会关闭这两个文件,并删除文件userScores.txt。最后,我们将会把userScores.tmp重命名为userScores.txt。
明白了吗?尝试用代码写出来吧。
练习题4:生成问题
现在我们已经进行到程序最重要的部分了,生成数学问题。准备好了吗?
为了生成一个数学问题,首先我们需要生成三个变量:两个列表和一个字典。
我们可以把两个列表命名为operandList和operatorList。
operandList列表应该存储五个数字,并把0作为它们的初始值。operatorList列表应该存储四个字符串,并以’'作为它们的初始值。
字典包含了4对信息,其中整数1到4是该字典的关键字,“+”,“-”,“\ *”,“**”是该字典的数据。让我们把该字典命名为operatorDict。
[练习4.1:使用随机数字更新operandList]
首先我们需要使用randint()函数生成随机数字,并用这些随机数字替换operandList列表的初始值。
randint()函数接收两个参数,start和end,并返回一个随机整数N,N满足 start<=N<=end。
比如,如果调用randint(1,9),它将会从数字1,2,3,4,5,6,7,8,9这些数字中随机返回一个整数。
为了使用随机数字更新我们的operandList列表的值,我们可以一个一个地事新,因为operandList列表仅仅只有五个成员。我们可以这么写。
operandList[0] = randint(1, 9)
operandList[1] = randint(1, 9)
operandList[2] = randint(1, 9)
operandList[3] = randint(1, 9)
operandList[4] = randint(1, 9)
每次调用andint(1,9),它将会从1,2,3,4,5,6,7,8,9这些数字中编机放回一个整数。
然而,这并不是更新operandList列表最优雅的方式。想象一下如果aperandList列表有1000个成员,更新数据将会是多么麻烦。更好的一种方式是使用for循环。
尝试使用一个for循环来完成这个任务。
完成了吗?
[练习题4.2:用数学运算符号更新operantList]
既然已经有了可以操作的数字,我们就需要为我们的问题生成随机的数学运算符号(+,一,*,**)。为了达到这个目的,我们将会使用randint()函数和operatorDict字典。
randint()将会生成字典的关键字,这在随后会使用operatorDict字典来映射到正确的运算符号。比如,为了分配符号给operatorList[0],我们可以这么写。
operatorList[0] = operatorDict[randint(1,4)]
和练习题4.1类似,你应该使用一个for循环来完成这个任务。然而,有一个问题让这个练习题比练习题4.1更难。
回想下在Python中,** 代表着指数,即2 ** 3=2^3?
问题是当我们在Python中有两个连续的指数操作时,比如2 ** 3 ** 2,Python会把它翻译成2 ** (3 ** 2),而不是(2 ** 3) ** 2。在第一种情况下,结果是2的9次方,即2 ^ 9, 是512。在第二种情况下,结果是8的2次方,即8^2,是64。因此当我们遇到像2 ** 3 ** 2这样的问题时,如果它被翻译成(2 ** 3) ** 2时,用户就会得到错误的结果。
为了防止这样的问题发生,我们要修改代码,这样我们就不会得到两个连续的 ** 符号。换句话说,operatorList=[‘+’,‘+’,‘-’,‘**’]是没问题的,但operatorlist=[‘+’,‘-’,’ ** ‘,’ ** ']就不行了。
这个练习题是所有练习题中最难的一个。尝试找到一个解决两个连续**符号的方法。一旦你找到了,我们就可以开始进入练习题4.3了。
提示:如果遇到困难,你可以考虑在for循环里使用一个if语句。
[练习题4.3:生成一个数学表达式]
既然已经有了操作符号和操作数据,我们就可以尝试生成一个字符串类型的数学表达式。这个表达式使用operandList函数的五个数字,以及operatorList中的四个数学符号来生成一个问题。
我们已经声明了另一个叫作questionString的变量,并把数学表达式分配给questionString。questionString的样例包括:
6-2* 3-2** 1
4+5-2* 6+1
8-0* 2+5-8
尝试亲手生成这样的表达式。
提示:
你可以使用一个for循环来连接operandList和operatorList中的单独的子字 符串来得到数学表达式。
[练习题4.4:计算结果]
现在我们应该已经有了一个字符串式的数学表达式,分配给变量questionString。为了计算这个表达式的结果,我们将会使用一个Python中内建的高明的函数,eval()。
eval()会把字符串翻译成代码并执行。比如,如果这么写eval(“1+2+4”),我们将会得到数字7。
因此为了计算我们的数学表达式的值,我们把questionString传递给eval()函数,并把结果分配给一个新的叫作result的变量。
这个练习题非常简单,只需要一步就可以完成了。
[练习题4.5:与用户交互]
最后,我们将会与用户交互。
在本练习题中,我们将会做一些新的事情。
- 第一步:把问题显示给用户。
- 第二步:提示用户输入答案。
- 第三步:计算答案,并显示合适的信息,然后返回用户的得分。
对于第一步,我们需要使用内建的函数来操作字符串。和前面提到的一样,在Python中,** 符号表示指数操作。也就是说,2** 3=8。然而,对于大多数用户来说,** 没有什么意思。因此如果我们显示一个像2** 3+8-5这样的一个问题,用户将有可能困惑。为了防止这个问题的发生,我们将把questionString中的所有** 符号替换为^符号。
为了做这个,我们将使用内建的函数replace()。使用这个函数非常简单直接,仅需要这么写questionString=questionString.replace(“**”,“^”)。现在你就可以给用户打印得到的数学表达式了。
对于第二步,你可以使用input()函数来获取用户的输入。
对于第三步,你应该使用一个if语句来计算答案,并显示正确的信息。如果用户的答案是正确的,我们将恭喜用户并返回值1。如果用户的答案是错的,我们将显示正确的答案,并返回值0。
回想一下input()函数返回的用户输入是一个字符串。因此,当你比较用户的输入和正确的答案(在练习题4.4中得到的)时,你需要做一些类型转换,把用户输入的值转换为一个整数。当转换用户输入为整数时,你应该使用一个try,except语句来检查用户输入的是否为一个数字。如果用户输入的是一个字符串,程序应该可以通知用户这个错误,并提示用户输入一个数字。
你可以使用一个while True循环,只要用户输入的不是数字,就一直提示用户输入一个数字。while True的语句和写的类似while1==1是一样的。因为1总是等于1(因此总是True),循环将会一直运行。
下面是为本练习题如何使用一个whileTrue循环的建议。
while True:
try:
把用户的答案转换为一个整数,并比较这个结果
根据结果返回用户的得分
except:
如果转换失败,打印出错误信息
提示用户再次输入答案
while True循环将会一直循环,因为while条件总是True。循环在try代码块执行正确的情况下才会退出,并到达return语句。
尝试这个练习题。一旦完成后,你就可以进入第二部分,我们会在第二部分编写一个真正的程序。
第二部分:mathGame.py
恭喜完成了第一部分,欢迎进入第二部分。第二部分将会轻而易举,因为我们仅仅需要调用我们在前面定义的函数。
练习题5:编写主程序
首先,我们将在一个try,except语句中完成主程序。我们想要在运行主程序时,处理任何无法预见的错误。
我们开始为try代码块编写代码。
首先,我们需要引入myPythonFunctions模块。接下来,我们要提示用户输入他/她的用户名,并把这个值分配给变量userName。把这个变量作为一个参数传递给getUserScore()中。
getUserScore()要么返回用户的得分,要么返回“-1”(如果用户不存在的话)。让我们把这个值转换为一个整数,并把它分配给变量userScore。
现在,我们需要设置另一个变量newUser的值。如果用户没有找到,newUser=True,否则newUser=False。如果newUser=True,我们需要把userScore的值从-1改为0。
我们的程序的下一部分包含了一个while循环。具体来说,我们的程序将会提示用户输入,并确定他是否应该终止程序,还是做些别的事情。
- 步骤一:
你需要声明另一个变量userChoice,并给它赋初始值0。
- 步骤二:
接下来,使用一个while循环,比较userChoice和你选择的一个字符串,如“-1”。如果userChoice和“-1”不一样,调用函数generateQuestion()来生成一个新的问题。
- 步骤三:
generateQuestion()将会返回用户从这个问题的得分情况。使用这个结果来更新变量userScore。
- 步骤四:
最后,为了防止出现死循环,我们需要在while循环里再次使用input()函数,来接收用户的输入,并使用它来更新userChoice的值。
明白了吗?尝试编写代码吧。真正的编码将会让一切情况更加清楚。
最后,在while循环终止后,下一步是更新userScores.txt文件。为了这么做,我们仅需要调用函数updateUserPoints()。
就这样就可以了!一旦我们完成这一步,你将会有一个完整的程序,你写的Python的第一个程序。尝试运行程序mathGame.py。它按预期工作了吗?激动吗?我真心希望你能和我一样激动。
挑战自我
我们已经来到了本章的结尾,真心希望你已经成功编写完成了你的第一个程序。如果你完成任何练习题有问题,你可以研究下附录E中的答案。通过研究别人的代码,你会从中学到很多。
在这一部分,我有三个额外的练习题,让你挑战自己。
挑战练习题1
在我们目前已经编写的程序中,我一直避免使用除号。你可以修改一下程序,让它也可以生成一个带除号的问题吗?你该如何检查用户的答案和正确答 案呢?
提示:
查明round()函数。
挑战练习题2
有时,生成的问题可能会得到一个非常大或者非常小的答案。比如,问题6*(8^ 9/1)^ 3将会得到答案1450710985375550096474112。
对于用户来说计算并输入这么大的数字不太方便。因此,我们需要避免太大或者太小的答案。你通过修改程序,能阻止问题生成大于50000或者小于-50000答案的问题吗?
挑战练习题3
最后一个挑战练习题是最难的。
到目前为止,在生成的问题中是没有括号的。你可以修改程序,让生成的问题也可以使用括号吗?一个这样问题的例子是2+(3*7-1)+5。
祝你在这些练习题中玩得愉快。建议的方法提供在附录E中。