本节书摘来自华章计算机《从问题到程序:用Python学编程和计算》一书中的第2章,第2.7节,作者 裘宗燕,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
2.7 判断和条件控制
数学里有一些表示判断的算符,如表示数值之间的等于或不等于、大于、小于等。Python提供了一些用于描述条件判断的运算符以及一种特殊的语句(称为if语言,条件语句),它能利用条件判断的结果选择执行不同的语句或语句序列。
2.7.1 条件判断和逻辑表达式
对于数值类型,这里有一组比较运算符用于描述基本条件判断,包括:
用一个比较运算符和两个数值表达式,可以构造出一个关系表达式,其意义就是判断两个数值之间的特定关系是否成立。
比较运算符也是一类运算符,求值一个关系表达式,也应该得到一个运算结果。Python用两个特殊的逻辑对象表示比较的结果,它们属于内置的逻辑类型bool,是该类型中仅有的两个对象,关键字True和False分别表示这两个逻辑对象。在做比较时,关系成立就会得到True,表示结果是“真”;关系不成立时将得到False,表示比较的结论是“假”。例如,当20 > 15将得到True,而12 >= 15得到False。
用一个关系运算符可以描述两个数值(表达式)之间的一种关系,但是这往往不够。很多时候我们希望考虑若干关系都成立等一些组合情况。例如,对于三角形的三条边,基本要求是它们的值都大于0,为此就需要写一个组合判断。逻辑研究表明,只需要三个逻辑组合运算,就足以描述所有的组合判断,它们是:
1)A与B,表示A和B都成立的组合判断;
2)A或B,表示A与B之中至少有一个成立的组合判断;
3)非A,表示A不成立的组合判断。
Python为上面三个逻辑组合关系提供了三个运算符:
其中,or和and是二元运算符,not是一元运算符。假设A和B是具有逻辑值的表达式(比较表达式或组合逻辑表达式),当且仅当A和B都为真时A and B为真;当且仅当A和B中至少一个为真时A or B为真;当且仅当A为假时not A为真。这三个逻辑组合运算符都是关键字,基于关系和逻辑运算符构造起来的表达式称为逻辑表达式。
例如,3 > 2 and 7 <= 10的值是True,因为3 > 2和7 <= 10都是正确的。not 3 > 2 or 7 > 10 or 7 > 5的值也是True,有关情况请读者自己分析。显然这里也有运算符的优先级和结合性问题。
为方便书写,Python允许连续使用比较运算符。例如x > 1 and x < 10可以简单地写为1 < x < 10。实际上,在Python里还可以写10 < x > y2,表示x > 10 and x > y2。这种形式在数学中不常见。
Python关于比较运算符和逻辑运算符的优先级和结合性的规定如下:
- 比较运算符的优先级低于算术运算符,因此x + y > x2相当于(x + y) > (x2),是合法的关系表达式。所有比较运算符的优先级相同,连续的比较表示对相邻运算对象做关系运算之后用and连接,相互为“与”关系。
- 三个逻辑运算符中,一元运算符not的优先级最高,但低于比较运算符,or的优先级最低,and介于两者之间。因此,not x2 > 5 or y < 6 and x y > 8相当于not (x2 > 5) or ((y < 6) and (x y > 8))。
例如,变量a、b、c的值构成一个三角形的三条边,条件可以写为:
a > 0 and b > 0 and c > 0 and \
a + b > c and b + c > a and a + c > b
这里假定了a、b、c的值都是数值。由于表达式很长,这里用了续行符,表示描述的是一个表达式,虽然其正文延续了两行。
2.7.2 if语句(条件语句)
要想根据逻辑判断选择不同的处理方式,就要使用if语句,这种语句也常被称为条件语句或者分支语句。if语句有两种基本形式,其一是:
在这个语句执行时,如果表达式为真,就执行语句组中的语句(可以有一个或多个),全部执行完后if语句结束;表达式为假时什么也不做,if语句直接结束。
第二种形式是:
如果语句执行时表达式为真,就执行第一个语句组中的语句;表达式为假时执行第二个语句组中的语句。执行完任一个语句组时整个if语句结束。
现在可以重写基于三边长求三角形面积的程序,加进输入检查:
# Program to calculate area of triangle
from math import sqrt
a = float(input("Length of edge a: "))
b = float(input("Length of edge b: "))
c = float(input("Length of edge c: "))
if a > 0 and b > 0 and c > 0 and \
a + b > c and b + c > a and a + c > b:
s = (a + b + c) / 2
area = sqrt(s * (s - a) * (s - b) * (s - c))
print("Area of the triangle:", area)
else:
print(a, b, c, "do not form a triangle!")
本程序一次运行的情况如下:
>>>
Length of edge a: 2
Length of edge b: 5
Length of edge c: 8
2.0 5.0 8.0 do not form a triangle
程序的第一行就是注释,也常被称为注释行。
if语句和程序格式
对一个if语句,从关键字if开始到相应语句组结束称为该语句的if段,从关键字else开始的部分称为其else段(else段可选,可以没有)。其中的“if 表达式:”部分和“else:”部分称为相应段的头部,语句组称为该段的体。
与前面各种简单语句相对应,if这类具有复杂结构(组合结构)的语句被称为复合语句,其中包含一些部分,有些部分本身也是语句。在if语句两个段的语句组里可以出现一个或多个任意的语句,包括if语句本身。这说明if语句的结构具有递归性,其中一些部分可具有与整体相同的结构,整个结构可以任意复杂。如果某语句组里只有一个语句,Python允许把它直接写在该段的头部之后,但人们不倡导这种写法。
为使程序清晰易读,Python语言对if这类组合结构的写法有严格要求,包括两方面:①处于同一层的不同成分必须相互对齐,②下一层的成分需要统一退格。如果一个子部分包含了顺序的多个语句,这些成分语句也必须相互对齐。
以if语句为例,如果它包含else段,if和else两个关键字必须在同一列上对齐(左对齐)。进而,两个语句块里的成分语句需要统一地退格并相互对齐。上面示例程序里的if语句及其成分都正确对齐了,这是必须的。这些要求是Python语言的特殊规定,多数语言没有这种要求。Python采用强制性的格式规定,是为了使程序里的语句结构清晰,具有较好的可读性。
初看起来贯彻上面的规定似乎很麻烦。但如果用IDLE编辑器(或其他专门针对Python的编辑器),保证格式的工作都很简单。IDLE能自动对齐同一层的语句,对于if等组合结构,它也能自动确定内层结构的对齐位置(多增加几个空格,可以自己设置。默认为4个字符,人们认为4个字符既足够清晰,也不带来大片空白,是比较好的设定)。此外,在IDLE编辑状态下按Tab键,IDLE会把输入光标移到下一对齐位置(通过加入几个空格);如果按Backspace键,IDLE就会把输入光标退到前一层对齐位置。做这两种移动时,光标所在的行不变。应特别注意:在编写Python程序时,不要自己用空格键在一行开始加空格,应直接利用IDLE的默认对齐方式,既方便又不会出错。
在编写Python程序时,应特别注意Tab字符和空格不能混用,因为它们都看不见,不易识别。采用非专门用于Python程序的编辑器编程序时,有可能出现这类错误,应该特别注意。程序里出现对齐错误时,解释器也看作语法错,可以定位和报告。
if语句的扩展形式
程序里也经常需要区分多种(两种以上)不同情况,分别处理。显然,这种需求可以用嵌套的if语句表示,即在一个if语句的语句组里再写if语句。Python为常见情况提供了扩展的if语句形式,允许用一系列逻辑表达式来区分多种情况:
也就是说,在语句的if段之后可以包含任意多个elif段,每个段包含形式为“elif 表达式:”的头部和一个语句块体。最后是else段。在执行这种if语句时,解释器先求值第一个表达式,如果其值为真就执行第一个语句组,然后整个if结束。如果第一个表达式的值是假,解释器就转去求值随后的elif段头部的表达式,如果其值为真就执行该elif段的语句组,然后整个if结束。如果被求值表达式还是假,解释器就继续考虑下一个elif段,直到在某步求得表达式的值为真,转去执行相应语句组。遇到所有表达式都是假的情况,解释器将执行else段的语句组(如果有这个段)。注意,无论有多少个elif段(及最后的else段),在if语句执行中至多执行一个段里的语句块。
下面将看到这种扩展结构的使用。显然,检查输入只是逻辑判断与条件语句的一种用途,正常计算中也经常需要分情况处理,这时就应该用条件语句。
2.7.3 编程实例
现在考虑几个编程实例。
求二次函数的根
计算二次函数的根是一项很常见的计算:给定二次函数的三个系数,根据公式算出函数的根。这里需要根据判别式的值区分三种情况,可能存在两个实根,或者存在一个重根,或者是无实根(存在一对共轭的复根)。下面只考虑实根的情况。
完成这一计算,应该先求出判别式的值,然后根据这个值分情况处理。根据二次函数的求根公式,不难写出下面的程序:
from math import sqrt
print("Program for root(s) of quadratic equetions.")
a = float(input("Coefficient of x**2: "))
b = float(input("Coefficient of x: "))
c = float(input("Constant: "))
d = b**2 - 4 * a * c
if d > 0:
tmp = sqrt(d)
print("Two roots:", (-b + tmp)/2/a, (-b - tmp)/2/a)
elif d == 0:
print("One root:", -b/2/a)
else:
print("No real root")
这里增加了第一个打印语句,告知用户这是什么程序,是一种提示。程序里使用了扩展的if语句,分三种情况分别处理。还需要注意的是除以2a的写法。
读者在前面已经看了不少程序执行情况。为了减少页面占用,今后除一些特殊情况外,书中将不再给出程序执行的情况。请读者自己运行程序查看效果。
简单总计
买一种货品需要从单价和数量算出总货款。考虑写一个程序完成这一简单计算。显然,计算中也需要输入数据。不难写出下面程序,其中做了必要的检查:
# A program calculating total price of some goods
name = input("Please give the name of the good: ")
price = float(input("Price of one piece of the good: "))
amount = int(input("Amount: "))
if price > 0 and amount > 0:
print(amount, "pieces of", name,
"totally: ", price * amount)
else:
print("Error. Price and amount should be positive.")
在这个程序里可以看到字符串的使用,以及如何结合使用字符串(字面量和保存在变量的字符串)和数值,生成较为复杂的输出形式。
当然,也可以利用字符串操作生成出同样的字符串,令print输出构造好的串。如果只是为了产生输出,仅利用print输出多个参数值的功能就能满足需要,像上面这样直接输出可能更方便。作为练习,请读者修改程序,利用字符串操作构造出同样的或更符合自己想法的字符串,而后调用print输出该串。