在前面的章节学习了一些Python语言的基础知识,不过到目前为止,这些Python代码都只是从上到下顺序执行的,很像批处理,从第一条语句一直执行到最后一条语句,然后程序退出。但在实际应用中,这样的程序几乎没什么实际的用处。因为有实用价值的程序需要有两个功能:选择和重复执行。其中"选择"就是根据不同的条件,执行不同的程序分支,这样程序才会有所谓的"智能",另外,"重复执行"也是程序的一个重要功能,计算机系统之所以完成机械工作的效率远比人类高,就是因为依靠强大的CPU和GPU不断重复执行程序。
"选择"和"重复执行"在编程语言中称为条件和循环。在Python语言中,条件使用if语句实现,而循环需要使用while或for语句,也就是说,Python语言中有两种循环语句。其实,包括前面讲的顺序结构,以及本章要讲的条件和循环,有一个统一的名称,叫作"控制流程",本章除了要介绍条件和循环语句外,还会讲一些Python语言中有趣的功能,例如,动态执行Python代码。
到目前为止,Python语句都是一条一条顺序执行的,在本节会介绍如何让程序选择是否执行代码块中的语句。
在讲条件语句之前,首先应该了解一下布尔类型。条件语句(if)需要为其指定布尔值或布尔类型的变量,才能根据条件判断是否要执行代码块中的语句。布尔值只有两个值:True和False,可以将这两个值翻译成"真"和"假"。
现在已经了解了布尔值是用来做什么的,但Python语言会将哪些值看作布尔值?其实在Python语言中,每种类型的值都可以被解释成布尔类型的值。例如,下面的值都会被解释成布尔值中的False。
这些值所涉及的数据类型有一些到现在为止并没有讲过(例如,[]表示长度为0的列表),不过读者也不用担心,在后面的章节会详细讲解这些数据类型。
如果在条件语句中使用上面的这些值,那么条件语句中的条件都会被解释成False,也就是说,条件代码块中的语句不会被执行。
在Python语言底层,会将布尔值True看作1,将布尔值False看作0,尽管从表面上看,True和1、False和0是完全不同的两个值,但实际上,它们是相同的。可以在Python控制台验证这一点。
>>> True == 1 True >>> False == 0 True >>> True + False + 20 21
很明显,可以直接将True看成1,False看成0,也可以直接将True和False当成1和0用,所以True+ False + 20的计算结果是21。
另外,可以用bool函数将其他类型的值转换为布尔类型的值。
>>> bool("") False >>> bool("Hello") True >>> bool([]) False >>> bool([1,2,3]) True >>> bool(20) True >>> bool('') False
可以看到,在前面给出的几个值会被系统认为False的值,通过bool函数的转换,会变成真正的布尔值。不过这些值是不能直接和布尔值比较的,例如,不能直接使用"[] == false",正确的做法是先用bool函数将其转换为布尔值,然后再比较:
bool([]) == false
在前面的代码中使用了"=="运算符,这是逻辑运算符,是二元运算符,需要指定左、右两个操作数用于判断两个值是否相等,如果两个操作数相等,运算结果为True,否则为False。这个运算符在后面的章节中会经常用到,当然,还有很多类似的运算符,在讲解条件语句时会一起介绍。
微课视频
代码位置:src\statement\if_demo.py
对于计算机程序来说,要学会的第一项技能就是"转弯",也就是根据不同的条件,执行不同的程序分支,这样的程序才有意义。
if语句的作用就是为程序赋予这项"转弯"的技能。使用if语句就需要用到代码块。代码块是Python语言与其他编程语言的最大差异,大多数编程语言都是使用一对大括号({…})或结束符(如end)建立代码块的,而Python语言需要通过缩进建立代码块,同一个代码块,缩进都是相同的。一般每次缩进用4个空格(尽量用空格,不要将制表符与空格混合使用,因为不同的IDE对制表符的解释是不一样的)。
if语句的语法格式如下:
其中logic expression表示逻辑表达式,也就是返回布尔类型值(True或False)的表达式。由于Python语句的各种数据类型都可以用作布尔类型,所以logic expression可以看作普通的表达式。根据代码块的规则,每个代码块的开始行的结尾要使用冒号(:),如果if代码块结束,就会退到代码块开始行的缩进量即可。
下面是if语句的基本用法。
在上面这段代码中,"n == 3"是逻辑表达式,本例中的值为True。而"print("n == 3")"是if代码块中的语句,由于"n == 3"的值为True,所以"print("n == 3")"会被执行。最后一条语句不属于if代码块,所以无论if语句的条件是否为True,这行代码都会被执行。
对于条件语句来说,往往分支不止一个。例如,上面的代码中如果变量n的值是4,那么if语句的条件就为False,这时要执行条件为False的分支,就可以使用else子句。
在上面这段代码中,n等于4,所以if语句的条件为False,因此else代码块中的语句会被执行。if与else都是代码块,所以if语句和else语句后面都要以冒号(:)结尾。
在多分支条件语句,需要使用elif子句设置更多的条件。elif后面跟逻辑表达式,elif也需要使用代码块,所以后面要用冒号(:)结尾。另外,在if语句中,if和else部分只能有一个,而elif部分可以有任意多个。
下面的例子通过raw_input函数从控制台输入一个名字,然后通过条件语句判断名字以什么字母开头。
程序运行结果如图3-1所示。
微课视频
代码位置:src\statement\nested_block.py
条件语句可以进行嵌套,也就是说,在一个条件代码块中,可以有另外一个条件代码块。包含嵌套代码块B的代码块A可以称为B的父代码块。嵌套代码块仍然需要在父代码块的基础上增加缩进量来放置自己的代码块。
下面的例子要求在Python控制台输入一个姓名,然后通过嵌套代码块判断输入的姓名,根据判断结果输出结果。
程序运行结果如图3-2所示。
图3-1 通过条件语句判断名字以什么字母开头
图3-2 嵌套代码块的输出结果
微课视频
代码位置:src\statement\comparison_operator.py
尽管if语句本身的知识到现在为止已经全部讲完了,不过我们的学习远没有结束。前面给出的if语句的条件都非常简单,但在实际应用中,if语句的条件可能非常复杂,这就需要使用本节要介绍的比较运算符。
先看表3-1列出的Python语言中的比较运算符。
表3-1 Python语言中的比较运算符
在表3-1描述的比较运算符中,涉及对象和容器的概念,目前还没讲这些技术,在本节读者只需了解Python语言可以通过比较运算符操作对象和容器即可,在后面介绍对象和容器的章节,会详细介绍如何利用相关比较运算符操作对象和容器。
在比较运算符中,最常用的就是判断两个值是否相等,例如,a大于b,a等于b。这些运算符包括"==""<"">"">=""<=""x != y"。
如果比较两个值是否相等,需要使用"=="运算符,也就是两个等号。
>>> "hello" == "hello" True >>> "Hello" == "hello" False >>> 30 == 10 False
要注意,如果比较两个字符串是否相等,会比较两个字符串中对应的每一个字母,所以"Hello"和"hello"并不相等,也就是说比较运算符是对大小写敏感的。
在使用"=="运算符时一定要注意,不要写成一个等号(=),否则就成赋值运算符了,对于赋值运算符来说,等号(=)左侧必须是一个变量,否则会抛出异常。
对于字符串、数值等类型的值,也可以使用大于(>)、小于(<)等运算符比较它们的大小。
>>> "hello" > "Hello" True >>> 20 > 30 False >>> s = 40 >>> s <= 30 False >>> "hello" != "Hello" True
Python语言在比较字符串时,会按字母ASCII顺序进行比较,例如,比较"hello"和"Hello"的大小。首先会比较'h'和'H'的大小,很明显'h'的ASCII大于'H'的ASCII,所以后面的都不需要比较了,因此,"hello" > "Hello"的结果是True。
如果一个字符串是另一个字符串的前缀,那么比较这两个字符串,Python语言会认为长的字符串更大一些。
>>> "hello" < "hello world" True
除了比较大小的几个运算符外,还有用来确定两个对象是否相等的运算符,以及判断某个值是否属于一个容器的运算符,尽管现在还没有讲到对象和容器,但这里不妨做一个实验,来看这些运算符如何使用,以便以后学习对象和容器时,更容易掌握这些运算符。
用于判断两个对象是否相等的运算符是is和is not,这两个运算符看起来和等于运算符(==)差不多,不过用起来却大有玄机。
>>> x = y = [1,2,3] >>> z = [1,2,3] >>> x == y True >>> x == z True >>> x is y True >>> x is z False >>> x is not z True
在上面的代码中,使用"=="和is比较x和y时结果完全一样,不过在比较x和z时,就会体现出差异。x == z的结果是True,而x is z的结果却是False。出现这样的结果,原因是"=="运算符比较的是对象的值,x和z的值都是一个列表(也可以将列表看作一个对象),并且列表中的元素个数和值完全一样,所以x == z的结果是True。但is运算符用于判断对象的同一性,也就是说,不仅对象的值要完全一样,而且对象本身还要是同一个对象,很明显,x和y是同一个对象,因为在赋值时,先将一个列表赋给y,然后再将y的值赋给x,所以x和y指向了同一个对象,而z另外赋值了一个列表,所以z和x、y尽管值相同,但并不是指向的同一个对象,因此,x is z的结果就是False。
判断某个值是否属于一个容器,要使用in和not in运算符。下面的代码首先定义一个列表变量x,然后判断变量y和一些值是否属于x。
in和not in运算符也可以用于判断一个字符串是否包含另外一个字符串,也就是说,可以将字符串看作字符或子字符串的容器。
>>> s = "hello world" >>> 'e' in s True >>> "e" in s True >>> "x" in s False >>> "x" not in s True >>> "world" in s True
如果遇到需要将多个逻辑表达式组合在一起的情况,需要用到逻辑与(and)、逻辑或(or)和逻辑非(not)。逻辑与的运算规则是:只有x and y中的x和y都为True时,运算结果才是True,否则为False。逻辑或的运算规则是:只有x or y中的x和y都为False时,运算结果才是False,否则都为True。逻辑非的运算规则是:在not x中,x为True,运算结果为False,x为False,运算结果为True。
>>> 20 < 30 and 40 < 50 True >>> 20 > 40 or 20 < 10 False >>> not 20 > 40 True
微课视频
代码位置:src\statement\assert.py
断言(Assertions)的使用方式类似于if语句,只是在不满足条件时,会直接抛出异常。类似于下面的if语句(伪代码):
那么究竟为什么需要这样的代码呢?主要是因为需要监测程序在某个地方是否满足条件,如果不满足条件,应该及时通知开发人员,而不是将这些bug隐藏起来,直到关键的时刻再崩溃。
其实在TDD(Test-driven development,测试驱动开发
)中经常使用断言,TDD会在程序发现异常时执行断言,并抛出异常。
在Python语言中,断言需要使用assert语句,在assert关键字的后面指定断言的条件表达式。如果条件表达式的值是False,那么就会抛出异常。而且断言后面的语句都不会被执行,相当于程序的一个断点。
可以看到,value变量的值是20,而assert后面的条件是value < 10 or value > 30,很明显,条件不满足,所以在断言处会抛出异常。而后面的断言,条件是value < 30,这个条件是满足的,所以在断言后面的语句都会正常执行。
当断言条件不满足时,抛出异常,在默认情况下,只显示了抛出异常的位置,并没有显示因为什么抛出异常,所以为了异常信息更明确,可以为assert语句指定异常描述。
微课视频
代码位置:src\statement\pass.py
Python语言是通过缩进表示代码块的,但这有一个问题,如果代码块中没有代码,那么该如何表示呢?
为了解决这个问题,Python语言提供了一个pass语句,用来实现空代码块。也就是说,在Python语言中,任何代码块必须至少有一条语句。如果代码块中没有实际的语句,那么就使用pass表示一条空语句。
下面的例子演示了pass语句的基本用法。
执行这段代码后,如果输入的不是Bill,那么将执行else分支。在该分支中,只有一条pass语句,所以else代码块是一个空的代码块。
现在已经知道了如何使用if语句让程序沿着不同的路径执行,不过程序最大的用处就是利用CPU和GPU强大的执行能力不断重复执行某段代码,想想Google的AlphaGo与柯洁的那场人机大战,尽管表面上是人工智能的胜利,但实际上,人工智能只是算法,人工智能算法之所以会快速完成海量的数据分享,循环功能在其中的作用功不可没。
对于初次接触程序设计的读者,可能还不太理解循环到底是什么。下面先看循环的伪代码。
1. 查看银行卡余额 2. 没有发工资,等待1分钟,继续执行1 3. Oh,yeah,已经发工资了,继续执行4 4. 去消费
可以看到,这段伪代码重复展示了一个循环到底是怎样的。对于一个循环来说,首先要有一个循环条件。如果条件为True,继续执行循环,如果条件为False,则退出循环,继续执行循环后面的语句。对于这段伪代码来说,循环条件就是"是否已经将工资打到银行卡中",如果银行卡中没有工资,那么循环条件为True,继续执行第1步(继续查看银行卡余额),期间会要求等待1分钟,其实这个过程可以理解为循环要执行的时间。如果发现工资已经打到银行卡上了,那么循环条件就为False,这时就退出循环,去消费。
在Python语言中,有两类语句可以实现这个循环操作,这就是while循环和for循环,本节将详细讲解这两类循环的使用方法。
为了更方便理解while循环,下面先用"笨"方法实现在Python控制台输出1~10共10个数字。
print(1) print(2) print(3) print(4) print(5) print(6) print(7) print(8) print(9) print(10)
可以看到,在上面这段代码中,调用了10次print函数输出了1~10共10个数字,不过这只是输出了10个数字,如果要输出10000个或更多数字呢?显然用这种一行一行写代码的方式实现显得相当笨重,下面就该主角while循环出场了。
现在就直接用Python代码解释一下while循环的用法。
可以看到,while关键字的后面是条件表达式,最后用冒号(:)结尾,这说明while循环也是一个代码块,因此,在while循环内部的语句需要用缩进的写法。
在上面的代码中,首先在while循环的前面定义一个x变量,初始值为1。然后开始进入while循环。在第1次执行while循环中的语句时,会用print函数输出x变量的值,然后x变量的值加1,最后while循环中的语句第1次执行完毕,然后会重新判断while后面的条件,这时x变量的值是2,x <= 10的条件仍然满足,所以while循环将继续执行(第2次执行),直到while循环执行了10次,这时x变量的值是11,x <=10不再满足,所以while循环结束,继续执行while后面的语句。
while循环是不是很简单呢?其实3.2.2节要介绍的for循环也并不复杂,只是用法与while循环有一些差异。
微课视频
代码位置:src\statement\loop.py
while循环的功能非常强大,它可以完成任何形式的循环,从技术上说,有while循环就足够了,那么为什么还要加一个for循环呢?其实对于某些循环,while仍然需要多写一些代码,为了进一步简化循环的代码,Python语言引入了for循环。
for循环主要用于对一个集合进行循环(序列和其他可迭代的对象),每次循环,会从集合中取得一个元素,并执行一次代码块。直到集合中所有的元素都被枚举(获得集合中的每个元素的过程称为枚举)了,for循环才结束(退出循环)。
在使用for循环时需要使用到集合的概念,由于现在还没有讲到集合,所以本节会给出最简单的集合(列表)作为例子,在后面的章节中,会详细介绍集合与for循环的使用方法。
在使用for循环之前,先定义一个keywords列表,该列表的元素是字符串。然后使用for循环输出keywords列表中的所有元素值。
上面这段for循环的代码非常容易理解,for语句中将保存集合元素的变量(keyword)与集合变量(keywords)用in关键字分隔。在本例中,keywords是集合,当for循环执行时,每执行一次循环,就会依次从keywords列表中获取一个元素值,直到迭代(循环的另一种说法)到列表中的最后一个元素if为止。
可能有的读者会发现,for循环尽管迭代集合很方便,但可以实现while语句对一个变量进行循环吗?也就是说,变量在循环外部设置一个初始值,在循环内部,通过对变量的值不断改变来控制循环的执行。其实for循环可以用变通的方式来实现这个功能,可以想象,如果定义一个数值类型的列表,列表的元素值就是1~10,那么不就相当于变量x从1变到10了吗!
如果使用这种方式,从技术上说是可以实现这个功能的,不过需要手工填写所有的数字太麻烦了,因此,可以使用一个range函数来完成这个工作。range函数有两个参数:分别是数值范围的最小值和最大值加1。要注意,range函数会返回一个半开半闭区间的列表,如果要生成1~10的列表,应该使用range(1, 11)。
下面的例子演示了使用顺序结构,while循环和for循环输出相邻数字的方法,其中for循环使用了range函数快速生成一个包含大量相邻数字的列表,并对这些列表进行迭代。
程序运行结果如图3-3所示。
图3-3 用while循环和for循环输出相邻数字
微课视频
代码位置:src\statement\loop_break.py
在前面介绍的while循环是通过while后面的条件表达式的值确定是否结束循环的,不过在很多时候,需要从循环体内部直接跳出循环,这就要用到break语句。
在上面的代码中,while循环的条件语句是x < 100,而x变量的初始值是0,因此,如果在while循环中,每次循环都对x变量值加1,那么while循环会循环100次。不过在while循环中通过if语句进行了判断,当x的值是5时,执行break语句退出循环。所以这个while循环只会执行6次(x为0~5),当执行到最后一次时,执行了break语句退出while循环,而后面的语句都不会调用,所以这段程序只会输出0~4共5个数字。
与break语句对应的还有另外一个continue语句,与break语句不同的是,continue语句用来终止本次循环,而break语句用来彻底退出循环。continue语句终止本次循环后,会立刻开始执行下一次循环。
在上面的代码中,当x等于1时执行了continue语句,因此,if条件语句后面的所有语句都不会执行,while循环会继续执行下一次循环。不过这里有个问题,当执行这段代码时,会发现进入死循环了。所谓死循环是指while循环的条件表达式的值永远为True,也就是循环永远不会结束。死循环是在使用循环时经常容易犯的一个错误。
现在分析一下这段代码。如果要让while循环正常结束,x必须大于或等于3,但当x等于1时执行了continue语句,所以if语句后面的所有语句在本次循环中都不会被执行了,但while循环最后一条语句是x +=1,这条语句用于在每次循环中将x变量的值加1。但这次没有加1,所以下一次循环,x变量的值仍然是1,也就是说,if语句的条件永远满足,因此,continue语句将永远执行下去,所以x变量的值永远不可能大于或等于3了。最终导致的后果是while循环中的语句会永远执行下去,也就是前面提到的死循环。
解决的方法也很简单,只要保证执行continue语句之前让变量x加1即可,或者将x += 1放到if语句的前面,或放到if语句中。
break和continue语句同样支持for循环,而且支持嵌套循环。不过要注意,如果在嵌套循环中使用break语句,那么只能退出当前层的循环,不能退出最外层的循环。
微课视频
代码位置:src\statement\loop_else.py
通过break语句可以直接退出当前的循环,但在某些情况下,想知道循环是正常结束的,还是通过break语句中断的,如果使用传统的方法,会有如下代码。
其实有更简单的写法,就是为while循环加一个else子句,else子句的作用仅是while正常退出时执行(在循环中没有执行break语句)。else子句可以用在while和for循环中。
下面的例子会在while和for语句中加上else子句,并通过一个随机整数决定是否执行break语句退出循环。如果程序是正常退出循环的(条件表达式为False时退出循环),则会执行else子句代码块。
要注意,由于这段代码使用了随机整数,所以每次执行的结果可能会不一样。
1.编写Python程序,实现判断变量x是奇数还是偶数的功能。
答案位置: src\statement\solution1.py
图3-4 判断x是奇数还是偶数的输入过程
2.改写第1题,变量x需要从Python控制台输入,然后判断这个x是奇数还是偶数,并且需要将这一过程放到循环中,这样可以不断输入要判断的数值。直到输入end退出循环。输入过程如图3-4所示。
答案位置: src\statement\solution2.py
3.编写Python程序,使用while循环输出一个菱形。菱形要用星号(*)打印。菱形的行数需要从Python控制台输入,行数必须是奇数。
当输入3时,会输出如图3-5所示的菱形,当输入13时,会输出如图3-6所示的菱形。
图3-5 打印3行菱形
图3-6 打印13行菱形
答案位置: src\statement\solution3.py
4.利用Python语言中的eval函数编写一个命令行方式的计算器,可以计算Python表达式,并输出计算结果。需要通过循环控制计算器不断重复输入表达式,直到输入end,退出计算器。输入过程如图3-7所示。
图3-7 计算器输入过程
答案位置: src\statement\solution4.py
本章主要介绍了流程控制语句(条件语句和循环语句),也可以将这种语句称为复合语句。通过设定条件,可以让程序沿着某个路径运行,并在一定条件下,不断重复执行某段程序,直到条件为False时退出循环。直到现在为止,Python程序最基础的部分才算告一段落,有了流程控制语句,就可以通过更复杂的操作控制后面涉及的数据结构了(列表、对象、集合等)。