[Python]-7-决策循环与异常处理

引言

这篇文章介绍python中的流程控制与异常处理。

文章目录

0×1.使用if做出决策

python的if语句与其他编程语言最大的区别解释每个分支后面都需要添加一个说明号,请看下面的实例:

					#!/usr/bin/env python3
					#coding=utf-8
					try:
					    a=input("请输入整数0:")
					    if int(a)==0:
					        print("www.qingsword.com")
					    elif int(a)>0:
					        print("a大于0")
					    else:
					        print("a小于0")
					except ValueError as err:
					    print(err)

					#这是一个简单的小程序,try...except语句用于捕获代码运行时的异常,也就是说,如果try中包含的语句执行出错,会跳转到except分支去匹配异常类型,本例仅提供了一种错误类型,即ValueError异常,当用户输入了一个非数字的时候,int(a)转换函数会抛出这个异常,这个异常会被print(err)语句打印在屏幕上,err是自定义的变量名,相当于ValueError异常信息的一个引用

					#input()函数会挂起程序,等待用户的输入,根据用户的输入执行if判断,如果等于0则打印出本站网址,如果大于0则匹配elif分支,如果小于0则匹配else分支,if语句块中可以包含多个elif分支用来匹配不同情况,当所有情况都不匹配时匹配else分支,if语句从上往下依次执行每个分支,只要其中一个分支匹配成功,执行完分支下的语句后,立即跳出if语句块,不再匹配后面的分支(就算后面的分支能够匹配,也不执行)
					

0×2.循环

python中有两种循环,while和for,请看下面的实例;

while循环实例:

					#!/usr/bin/env python3
					#coding=utf-8
					#导入python自带的随机数生成器
					import random
					
					#while后的条件为True就会一直循环
					while True:
					    #随机生成1到100中的一个数字
					    i=random.randint(1,100)
					    #如果这个数字小于50,continue语句会让流程跳转到while开始处继续循环
					    if i<50:
					        continue
					    #如果大于等于50,打印出这个数字,break语句会终止并跳出循环
					    print(i)
					    break

					#另一个while循环实例,num会从5递减,直到0时num大于0为假,终止循环
					num=5
					while num>0:
					    print(num)
					    num-=1
					

for循环实例:

					#!/usr/bin/env python3
					#coding=utf-8
					
					#从元组中依次读取每个元素打印
					for i in ("a","b","c"):
					    print(i)
					
					#range()方法接受三个参数,分别为range(起始数字,结束数字,公差),当仅设置一个参时,起始数字默认为0,公差默认为1,本例i将从0读取到4,但不会包含4,所以打印出的数字应该是0,1,2,3
					for i in range(4):
					    print(i)

					#设置了起始数字为1,结束数字为4,打印出1,2,3
					for i in range(1,4):
					    print(i)

					#公差为2,打印出0到11之间的所有偶数
					for i in range(0,11,2):
					    print(i)

					#for循环依次读取列表数据的实例,len()计算出a的长度为3,range(3)相当于(0,1,2)这样的元组列表,print函数打印出列表中每个索引位置所对应的元素
					a=["www.qingsword.com","晴刃","qingsword"]
					for i in range(len(a)):
					    print(str(i)+"~"+a[i])

					#除了能够使用上面这种方法打印出每个元素对应的索引外,python还提供了一个enumerate函数,能够直接将列表分解成索引对应元素的形式,例如
					#!/usr/bin/env python
					#coding=utf-8
					L=["a","b","c","d"]
					print(tuple(enumerate(L)))
					for i,a in enumerate(L):
					    print(i,a)

					#程序输出
					((0, 'a'), (1, 'b'), (2, 'c'), (3, 'd'))
					0 a
					1 b
					2 c
					3 d
					

for迭代字典实例:

					#!/usr/bin/env python
					#coding=utf-8

					d={"one":1,"two":2,"three":3,"four":4}
					#默认情况仅遍历字典的key,这等同于"for a in d.keys()"
					for a in d:
					    print(a)
					#如果想要遍历值,可以使用下面的方法
					for a in d.values():
					    print(a)
					#如果同时想遍历键值,可以在in之前使用两个变量
					for a,b in d.items():
					    print(a,b)

					#程序输出
					one
					two
					four
					three
					1
					2
					4
					3
					one 1
					two 2
					four 4
					three 3

					#for中携带多个变量不仅可以用在字典中,也能用在多维列表或元组中,例如
					#!/usr/bin/env python
					#coding=utf-8
					L=[[1,11,111],[2,22,222],[3,33,333]]
					for x,y,z in L:
					    print(x,y,z)

					#程序输出
					1 11 111
					2 22 222
					3 33 333
					

for迭代字符串实例:

					#!/usr/bin/env python
					#coding=utf-8

					s0="www.qingsword.com"
					s1=""
					for a in s0:
					    s1+=a
					print(s1)

					#程序使用for循环每次读取s0中的一个字符并将它添加到s1中,完成s0到s1的复制
					

Ps:while循环中的continue和break语句同样适用于for循环。

0×3.迭代器

列表,字典,元组,字符串都是可迭代类型,那么如何判断哪种数据类型可迭代呢?可以使用collections模块的Iterable类型来判断,例如:

					#!/usr/bin/env python
					#coding=utf-8
					from collections import Iterable

					print(isinstance(123,Iterable))
					print(isinstance("www.qingsword.com",Iterable))
					print(isinstance(["www.qingsword.com","qingsword"],Iterable))

					#程序输出,从输出可以看到,整型123不能迭代,所以返回False,后面的字符串与列表都是可迭代类型
					False
					True
					True
					

这些可以直接作用于for循环的对象统称为可迭代对象(Iterable),而生成器(列表生成器和函数生成器)不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误。可以被next()函数调用并不断返回下一个值的对象称为迭代器(Iterator);可以使用isinstance()判断一个对象是否是Iterator对象:

					#!/usr/bin/env python
					#coding=utf-8
					from collections import Iterator
					print(isinstance([],Iterator))
					print(isinstance("www.qingsword.com",Iterator))
					print(isinstance({},Iterator))
					print(isinstance((x for x in range(3)),Iterator))

					#从下面的输出可以看到,列表字符串和字典,虽然都是可迭代对象(Iterable),但他们都不是迭代器(Iterator),只有最后的列表生成器才是迭代器(是列表生成器而不是列表生成式,注意两者括号上的区别,一个是中括号,一个是圆括号,并且两者工作方式不一样)
					False
					False
					False
					True
					

在Python中,迭代器保存了一组算法,这组算法可以计算出一个数据流,这个数据流可以无穷大(比如实数集合),也可以是有限的数据流,迭代器对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误,所以迭代器对象是当我们访问这个迭代器时才计算出数据流中的下一个值。

python允许使用iter()函数,将一个可迭代对象转换成迭代器,例如:

					#!/usr/bin/env python
					#coding=utf-8
					from collections import Iterator
					print(isinstance(iter([]),Iterator))
					print(isinstance(iter("www.qingsword.com"),Iterator))

					#程序输出
					True
					True
					

for循环与迭代器的比较如下:

					#!/usr/bin/env python
					#coding=utf-8
					L1=[1,2,3]
					L2=iter(L1)
					
					#for循环迭代
					for a in L1:
					    print(a)
					
					#使用next()读取迭代器L2中的值(也可以使用for循环)
					while True:
					    try:
					        print(next(L2))
					    except StopIteration as err:
					        break
					

从这个实例可以看出,Python的for循环与调用next()函数访问迭代器,他们本质上是一致的。

0×4.异常捕获与处理

关于异常处理的语句在if中已经简单演示过,try语句块和if一样,可以包含多个except分支用于捕获异常,在try末尾还能添加一个else分支,当没有捕获到异常时将会运行这个分支下的内容,可能有朋友会问:"我怎么会知道异常名称是什么?什么时候会发生什么异常?",当我们使用解释器运行程序的时候,总会遇见一些错误,发生错误时解释器会返回一个异常信息,其中就包含了异常名称,我们只需要将这些名称记录下来,然后使用except捕获他们即可,请看下面的实例:

					#!/usr/bin/env python3
					#coding=utf-8

					d={1:"banana",2:"apple",3:"orange",4:"durian"}
					try:
					    i=input("输入你需要的水果编号(1-4):")
					    i=int(i)
					    if i==1:
					        print(d[i])
					    elif int(i)==2:
					        print(d[i])
					    elif int(i)==3:
					        print(d[i])
					    elif int(i)==4:
					        print(d[i])
					    else:
					        print(d[i])
					except ValueError as err:
					    print(err)
					except KeyError as err:
					    pass
					else:
					    print("未捕获到异常")
					finally:
					    print("End")

					#异常处理语句块可以包含多个except用来捕获不同的异常,当不需要对捕获到的异常进行处理时,可以在分支中输入pass,代表不做任何操作,try语句块的末尾可以跟随一个else分支,当没有捕获到异常时会执行这个分支,但一般不需要添加这个分支,同时还可以添加一个finally分支,不论有没有捕获到异常,这个分支都会被执行,当程序执行过程中遇到异常时,如果添加了异常处理语句,会直接跳转到异常分支进行处理,而不继续执行下面的语句,如果没有任何异常处理能够匹配,程序会因为出错而直接终止运行

					#执行上面的程序,当输入一个非数字的字符时,会触发ValueError异常(这个ValueError就是异常名),并且打印出异常信息(这个异常是在i=int(i)时发生的,因为要被转换的字符不是数字)
					输入你需要的水果编号(1-4):a
					invalid literal for int() with base 10: 'a'

					#当输入一个范围不在1-4中的数字时,会触发KeyError异常,这个异常是在if块的else分支下的print(d[i])语句中发生的,假如我输入的是6,d[6]在字典中根本找不到对应的键,所以抛出"键错误",但是这条分支下面使用了pass,这会让程序静默处理,不会在屏幕上产生任何输出

					#当程序没有异常时,输出如下
					输入你需要的水果编号(1-4):1
					banana
					未捕获到异常
					End
					

除了使用try捕获异常外,还能够使用raise来主动的抛出一个异常,例如:

					#!/usr/bin/env python3
					#coding=utf-8
					#创建一个绝对值转换函数
					def my_abs(x):
					    """绝对值转换函数,如果传入的不是一个整数或浮点数,则使用raise关键字抛出一个TypeError异常,isinstance()函数接收两个参数,第一个参数是要判断的传入值,第二个参数是一个判断范围,本例判断x是否为整形和浮点型"""
					    if not isinstance(x,(int,float)):
					        raise TypeError("Bad operand type.")
					    elif x>=0:
					        return x
					    else:
					        return -x

					print(my_abs(-123))
					print(my_abs(23))
					print(my_abs(-3.14))

					#程序输出
					123
					23
					3.14

					#如果传入一个错误的值,就会得到一个TypeError
					print(my_abs("www.qingsword.com"))
					TypeError: Bad operand type.
					

有时候我们捕获到了一种错误,但却想抛出另外一种类型的错误(虽然这种情况并不多见),也可以使用raise关键字,例如:

					#!/usr/bin/env python3
					#coding=utf-8
					def A(x=233):
					    return x/0 #会遇到一个被除数不能是0的错误
					try:
					    A()
					except ZeroDivisionError as err:
					    #捕获这个错误后,自定义抛出一个TypeError错误
					    raise TypeError("一般,不要抛出完全不同类型的错误")
					

上面这种抛出一个完全不同类型的错误并不是推荐的做法,除非你知道自己在做什么,正确的抛出错误方法如下:

					#!/usr/bin/env python3
					#coding=utf-8
					def A(x=233):
					    try:
					        return x/0
					    except ZeroDivisionError as err:
					        print("被除数不能是0")
					        raise #如果不带参数,就会把当前错误原样抛出
					A()
					

再来看一个错误实例,Python允许使用try捕获错误,但如果没有使用try语句,那么错误会从源头开始一层层返回,直到被python解释器捕获,例如下面的程序:

					#!/usr/bin/env python3
					#coding=utf-8
					def A(x):
					    return x/0 #错误的发源,0不能是被除数
					def B(x):
					    return A(x)*2
					def C():
					    B(0)
					
					#当我们执行C()的时候,程序就会报错
					C()
					print("End") #这一句没有被执行,程序出错后就终止了

					#错误输出
					File "test2.py", line 10, in <module>
					  C() #首先判断出错误是因为这个语句引起
					File "test2.py", line 8, in C
					  B(0) #但这个错误是因为C的B(0)语句
					File "test2.py", line 6, in B
					  return A(x)*2 #错误又被追溯到B中的return语句
					File "test2.py", line 4, in A
					  return x/0 #最后才追溯到A中的return语句
					#打印出错误类型是ZeroDivisionError,被除数不能是0
					builtins.ZeroDivisionError: division by zero
					

我们注意到上面的程序在除错后就直接终止了,但实际情况中我们可能需要记录并打印出这些错误信息,但仍然让程序继续运行直到结束,可以使用日志记录模块logging来完成这些操作,修改上面的程序如下:

					#!/usr/bin/env python3
					#coding=utf-8
					import logging #引入日志模块
					def A(x):
					    return x/0
					def B(x):
					    return A(x)*2
					def C():
					    try:
					        B(0)
					    except Exception as err:
					        logging.exception(err)  #记录并打印捕获到的错误

					C()
					print("end")

					#将logging.exception写在except中能够记录并打印出错误日志,输出这些错误日志信息后,程序会继续执行,最后打印出end(日志记录模块比print更加详细的记录了错误的调用步骤,让我们能够明白错误的源头在哪里,如果只是print(err)就只会打印出division by zero),下面是程序输出
					ERROR:root:division by zero
					Traceback (most recent call last): #错误跟踪信息
					  File "test2.py", line 10, in C
					    B(0)
					  File "test2.py", line 7, in B
					    return A(x)*2
					  File "test2.py", line 5, in A
					    return x/0
					ZeroDivisionError: division by zero
					end  #程序报错后继续执行,直到完结
					

大家可能注意到了我们捕获的不同错误都有错误名称,这些名称在Python中是分层设计的(错误其实也是类,所有的错误类型都继承自BaseException),不同的错误有它们的父类和子类,父类的捕获优先级高于子类,也就意味着我们在except中如果设置了一个父类的名称,那么它将捕获所有子类错误,而再写一个except捕获它的子类就没有意义了,也不永远不会被执行,下面这个列表列出了所有的错误类,缩进的是子类,通过这个列表不难看出,为什么在上面的实例中通过捕获Exception错误就能捕获到ZeroDivisionError错误,因为ZeroDivisionError是Exception的子类:

					BaseException
					 +-- SystemExit
					 +-- KeyboardInterrupt
					 +-- GeneratorExit
					 +-- Exception
					      +-- StopIteration
					      +-- StopAsyncIteration
					      +-- ArithmeticError
					      |    +-- FloatingPointError
					      |    +-- OverflowError
					      |    +-- ZeroDivisionError
					      +-- AssertionError
					      +-- AttributeError
					      +-- BufferError
					      +-- EOFError
					      +-- ImportError
					      +-- LookupError
					      |    +-- IndexError
					      |    +-- KeyError
					      +-- MemoryError
					      +-- NameError
					      |    +-- UnboundLocalError
					      +-- OSError
					      |    +-- BlockingIOError
					      |    +-- ChildProcessError
					      |    +-- ConnectionError
					      |    |    +-- BrokenPipeError
					      |    |    +-- ConnectionAbortedError
					      |    |    +-- ConnectionRefusedError
					      |    |    +-- ConnectionResetError
					      |    +-- FileExistsError
					      |    +-- FileNotFoundError
					      |    +-- InterruptedError
					      |    +-- IsADirectoryError
					      |    +-- NotADirectoryError
					      |    +-- PermissionError
					      |    +-- ProcessLookupError
					      |    +-- TimeoutError
					      +-- ReferenceError
					      +-- RuntimeError
					      |    +-- NotImplementedError
					      |    +-- RecursionError
					      +-- SyntaxError
					      |    +-- IndentationError
					      |         +-- TabError
					      +-- SystemError
					      +-- TypeError
					      +-- ValueError
					      |    +-- UnicodeError
					      |         +-- UnicodeDecodeError
					      |         +-- UnicodeEncodeError
					      |         +-- UnicodeTranslateError
					      +-- Warning
					           +-- DeprecationWarning
					           +-- PendingDeprecationWarning
					           +-- RuntimeWarning
					           +-- SyntaxWarning
					           +-- UserWarning
					           +-- FutureWarning
					           +-- ImportWarning
					           +-- UnicodeWarning
					           +-- BytesWarning
					           +-- ResourceWarning