第5章程序流控制与异常处理 本章主要介绍程序流的逻辑控制,以及程序流发生异常的处理方法。 本章的学习目标: 掌握if、while、for程序流控制流程; 掌握异常的捕获try…except…finally的使用。 5.1Python程序控制流 Python源程序代码是根据一定的逻辑条件运行的程序流,程序流包括顺序语句、条件语句和循环语句。顺序语句按照源程序代码的先后顺序,从上到下依次运行每一条程序语句。顺序结构是程序的最基本结构(见图5.1)。 图5.1顺序结构 条件语句(if)和循环语句(while、for)是根据一定的逻辑条件(True或者False)来选择后续运行的代码块。 提示: Python无switch或case语句。因为按照Python之“禅(Zen)”宗旨,该语句属于多余。 5.2if条件语句 Python语言中if条件语句是最基本的逻辑判断程序流控制语句。语句的关键词为if…elif…else,关键字elif是else if的缩写。其格式为: ifC1: 程序代码1 elif C2: 程序代码2 else: 程序代码3 if条件语句的语法说明: (1) if条件后面用冒号“:”表示满足条件后要运行的语句块。 (2) if条件语句可以有0~n(任意)个elif,而不用else if。 (3) if条件语句结束可以有0~1个else语句。 (4) if语句可以嵌套,使用缩进空格数来区分嵌套语句块。 if条件语句的程序流程图如图5.2所示。 图5.2if条件语句的程序流程图 图5.2中的if条件语句程序运行流程如下: (1) 如果C1为True,将运行“程序代码1”块语句。 (2) 如果C1为False,将判断C2。 (3) 如果C2为True,将运行“程序代码2”块语句。 (4) 如果C2为False(else),将运行“程序代码3”块语句。 【例5.1】输入0~100任意成绩数据,通过if条件语句判断成绩的等级。 num = int(input('请输入分数: ')) if0<= num < 60:# 判断值是否在0~60 print ('挂科!') elif 60<=num<70:# 判断值是否在60~70 print ('及格') elif 70<=num<80:# 判断值是否在70~80 print ('中') elif 80<=num<90:# 判断值是否在80~90 print ('良好') elif 80<=num<=100:# 判断值是否在90~100 print ('优秀') else: print ('输入有误!') 提示: 程序如果只需要条件赋值,可以用if的简写形式。 如果字符串s的长度大于4,把s赋值给b,否则输出'too short'。 s= input('请输入一个长度大于4的字符串: ') b=s if len(s)>4 else 'too short' 如果a等于10,输出'等于10',否则输出'不等于10'。 a=23 print('等于10') if a ==10 else print('不等于10') 5.3Python循环语句 Python语言中有两种类型的循环语句,即while循环语句和for 循环语句。 提示: Python无do…while循环语句。因为按照Python之“禅(Zen)”,它属于多余的。 5.3.1while循环语句 运行while循环语句,程序先判断循环条件,如果条件满足,则进入再循环。 while循环语句的语法说明: (1) while循环语句后面用冒号“:”表示满足条件后要运行的语句块。 (2) while循环语句可以嵌套,使用缩进空格数来区分嵌套语句块。 (3) while循环语句可以有else(很少用)语句。 【例5.2】while循环语句示例。 # 用while循环语句,必须有一个控制逻辑条件 count=0 while count<3: # 循环就是重复运行循环体里面的代码 print(f'Round.{count} test!') count+=1 运行结果: Round.0 test! Round.1 test! Round.2 test! 5.3.2for…in循环语句 Python中的for…in 语句是另一种循环语句,该语句必须有一个可迭代(iterates)的对象才能循环。它会遍历序列(可迭代对象)中的每一个元素。 提示: Python的for语句是遍历任何可迭代的对象,如list、set等,而不是仅用来控制循环次数或循环条件,这与C和Java语言中的for语句有本质不同。 a = ['hello', 'world'] for i in range(len(a)): print(i, a[i]) 运行结果: 0 hello 1 world while和for循环语句可以包含else子句(虽然使用概率很低),即结束while或for循环后运行的语句。 【例5.3】for循环代码示例。 ''' for 迭代对象 in 序列: 代码块(一行语句或多行代码) else: 代码块(一行语句或多行代码) ''' while 条件: else: 代码块(一行语句或多行代码) ''' 示例代码: sites = ["Youtub", "Google","Baidu","Taobao"] for site in sites: if site == "sohu": break print("get " + site) else: print("没有循环数据!") print("完成循环!") 5.3.3break、continue、pass语句 图5.3循环与continue、 break控制语句 break语句与Java和C语言的break控制语句一样。即程序运行到break语句,中断循环、跳出break所在的最里层的循环体。 continue语句与Java和C语言的continue控制语句一样。即程序运行到continue,不再向下运行,直接再进入下一次循环。 关于continue和break语句的区别(见图5.3),可以举一个通俗的例子场景来表达。 使用continue语句的场景: 甲乙两人计划准备下5局象棋,两人在下到第3局中途时,乙看到本局败势已定,但乙并不服输,于是放弃第3局,而接着比赛下面第4、5局,这时使用continue语句。 使用break语句的场景: 甲乙两人计划下5局象棋,两人在下到第3局中途,乙看到败局已定,丧失了信心,直接放弃所有比赛,退出比赛,这时使用break语句。 【例5.4】continue语句代码示例。 # continue语句,跳过循环 a = '2123456' for letter in a: if letter == '2': continue print(letter) #运行结果: 1 3 4 5 6 break语句用来终止循环语句,即循环条件没有False条件或者序列还没被完全递归完,也会停止运行循环语句。 【例5.5】break语句代码示例。 # break语句,跳出循环 b = '1234567' for sam in b: if sam == '2': break print(sam) #运行结果: 1 pass语句是空语句,不运行任何操作。通常在开发程序的原型或调试程序时,用它来占位,将来用代码替换掉pass语句。 如: while 条件: pass 5.4异常 异常是一种事件, 它有可能在程序运行过程中发生,会改变程序的正常运行。Python程序运行时,可能 涉及输入输出、数学运算,以及对文件和网络资源的访问等。有时程序运行时,会遇到算 术运算除数为0、无权访问文件、网络连接中断等问题,程序会引发异常。必须在程序中捕获、处理这些异常,才能确保程序正确运行。 【例5.6】异常代码示例。 num=input('please input the number?') for i in range(int(num)): print(i) 上述程序在运行时,如果用户输入的是数字字符串,则可以得到正确结果,如果输入的是非数字,则程序会引发异常。 please input the number?ok ValueError: invalid literal for int() with base 10: 'ok' 5.4.1异常的处理 异常处理是通过使用try…except…finally语句来捕获、处理异常实现的。其语法为: try: <语句># 运行的代码 except <名字>: <语句># 如果在try代码块中,引发了'名字'异常 except <名字> as <数据>: <语句># 如果引发了'名字'异常,获得附加的数据 finally: <语句># 有没有异常,始终运行 1. 程序流程 (1) 运行try语句后,Python也在当前程序的上下文中做标记,这样当异常出现时就可以回到这里。如果运行try语句没有发生异常,则程序直接跳转到finally语句位置。 (2) 如果运行try后的语句发生异常,程序流就跳回到try语句,并运行第一个匹配该异常的except子句; 若没有该异常匹配的except子句,该异常将被递交到上层的try语句,或者到程序的最上层(这样将结束程序,并打印默认的出错信息)。 (3) 异常处理完毕,控制流就通过整个try语句(除非在处理异常时又引发新的异常)。finally: 无论系统有无异常代码均运行。 2. 异常说明 (1) except语句可以有多个,Python会按except语句的顺序依次匹配出现的异常,如果异常已经处理就不会再进入后面的except语句。 (2) except语句后面如果不指定异常类型,则默认捕获所有异常。可以通过logging或者sys模块获取当前异常。 (3) except语句可选,finally语句也可选,但是二者必须要有一个,否则try语句就没有意义了。 (4) except语句可以以元组形式同时指定多个异常。 【例5.7】异常捕获与处理代码示例。 while True: try: num=input('please input the number?') for i in range(int(num)): print(i) break except ValueError: print("Oops!That was no valid number.Try again ") finally: print('Whatever, I always run!') 运行结果: please input the number?ss Oops!That was no valid number.Try again Whatever, I always run! please input the number?3 0 1 2 Whatever, I always run! 异常的参数: while True: try: num=input('please input the number?') for i in range(int(num)): print(i) break except ValueError as arg : print(arg.args) finally: print('Whatever, I always run!') 一个异常可以带上参数,可作为输出的异常信息参数。 please input the number?rr ("invalid literal for int() with base 10: 'rr'",) Whatever, I always run! please input the number?2 0 1 Whatever, I always run! 变量接收的异常值通常包含在异常的语句中,保持在一个元组中(如上述例子错误("invalid literal for int() with base 10: 'rr'",)参数是一个元组),变量可以接收一个或者多个值。元组通常包含错误字符串、错误数字和错误位置。 5.4.2异常的抛出 Python程序除了可以捕获异常外,还可以在代码中使用raise语句主动抛出异常。raise语句的一般语法如下: raise [Exception [, args [, traceback]]] 其中: Exception是异常的类型(例如,NameError),args是异常参数的值。 参数是可选的; 如果没有提供,则异常参数为None。 最后一个参数traceback也是可选的(在实践中很少使用),如果存在,则用于异常的追溯对象。 示例: x=input('input x:') if (int(x)<5): raiseNameError('HiThere') 运行结果: raiseNameError('HiThere') NameError: HiThere raise唯一的一个参数指定了要被抛出的异常。它必须是一个异常的实例或者是异常的类(也就是 Exception 的子类)。 【例5.8】raise主动抛出异常代码示例。 try: x=input('input x:') if (int(x)<5): raise NameError('Hi There') print(x) except NameError as arg: print(arg) 运行结果: input x:4 HiThere 5.5断言的用法 在开发一个程序时,与其让它运行时崩溃,不如在它出现错误条件时就崩溃(返回错误)。这时断言 (assert)就显得非常有用。 assert的语法格式: assert expression 它的等价语句为: if not expression: raise AssertionError 【例5.9】assert代码示例。 >>> assert 1==1 >>> assert 1==0 Traceback (most recent call last): File "<pyshell#1>", line 1, in <module> assert 1==0 AssertionError 5.6with语句 with语句上下文管理器是一个对象,它定义了在运行with语句时要建立的运行时上下文(资源),它处理进入和退出所需运行时上下文,并运行代码块。它是对普通的try…except…finally使用模式进行封装以方便地重用。 with语句的语法为: with expression [as target] 参数说明: expression: 一个需要运行的表达式; target: 一个变量或者元组,存储的是expression表达式运行返回的结果,它是可选参数。 相比try…except…finally语句,with语句更加简洁。 【例5.10】使用with语句打开文本文件c:/a.txt,并显示文件内容。 with open('c:/a.txt',encoding='utf-8') as f : print(f.read()) 5.7综合案例 Python中有些对象是可变的,有些对象是不可变的。可以用函数hash(object)来判定对象是否可变,如果hash(object)有返回值的是不可变。hash(object)抛出异常的是可变对象。以下针对list、tuple、range、str、bytes、bytearry、memoryview、set、frozenset、dict十类对象进行可变与不可变对象测试。 【例5.11】Python中可变对象与不可变对象代码测试(通过代码自动判读对象是否可以改变)。 测试对象为: a=[1,2,'hello'] b=set('hello') c=dict(one=11, two=22, three=33) d=bytearray('hello',encoding='utf-8') e=(1,2,'hello') f=range(1,6,2) g='hello' h=bytes('hello',encoding='utf-8') i=memoryview(b'hello') j=frozenset('hello') 测试代码为: test=[a,b,c,d,e,f,g,h,i,j] for i in test: try: hash(i) print(f'{type(i)} 是不可变对象') except: print(f'{type(i)} 是可变对象') 图5.4对象种类 图5.4所示对象种类与下述测试结果相符。 <class 'list'>的实例是可变对象。 <class 'set'>的实例是可变对象。 <class 'dict'>的实例是可变对象。 <class 'bytearray'>的实例是可变对象。 <class 'tuple'>的实例是不可变对象。 <class 'range'>的实例是不可变对象。 <class 'str'>的实例是不可变对象。 <class 'bytes'>的实例是不可变对象。 <class 'memoryview'>的实例是不可变对象。 <class 'frozenset'>的实例是不可变对象。 5.8本章小结 通过本章的学习,可以掌握if、while、for语句对程序流的控制,掌握程序发生异常的处理方法。 5.9习题 扫码观看 (1) 已知上海地铁1、2号线(为了简化问题,只考虑这两条线路)两条地铁站线路各站点名称(可以到http://service.shmetro.com/axlcz01/index.htm获取),通过Python编程获得“上海南站”到“金科路”的换乘路线。 (2) 模拟双色球投注与兑奖。使用循环随机投注10000注,给定一组中奖号码,判断有多少注号码中奖,计算中奖概率。 (3) 给定一个字典,请按照逆序输出字典的key和value。