# 到目前为止,我们已经写过很多代码了,在调试程序的时候,也遇到过很多次"错误"。当程序遇到"错误"的时候,就会停止运行。而"错误"常常防不胜防。如果不对它们进行处理,程序未免太不"健壮"了。为了增强程序的稳健性,必须对它们进行处理。
知识技能导图
# -------------------------------- 错误 ------------------------------------#
# python中的"错误"可以分为两种:语法错误(syntax error)、异常(exception)。
# 语法错误通常是因为语句不符合python语法要求,解释器在解析的时候就会报错。比如:
for i in range(10)
# File "/Users/apple/PycharmProjects/pythonProject3/异常处理.py", line 6
# for i in range(10)
# ^
# SyntaxError: invalid syntax
# 上面那句话因为缺少":",导致解释器无法解释,于是报错。这个报错行为是由python的语法分析器完成的,并且检测到了错误所在文件和行号(File "/Users/apple/PycharmProjects/pythonProject3/异常处理.py", line 6),还以"^"标示错误位置(后面缺少":"),最后显示错误类型。
# 在程序中还会出现逻辑错误。逻辑错误可能是由于不完整或者不合法对策输入导致的,也可能是无法生成、计算等,或者其他逻辑问题。逻辑错误不是由python来检查的,所以此处所谈的错误不包括逻辑错误。
# 对于初学者而言,细节的错误常常令人烦恼,如代码块的缩进、丢掉冒号、单词拼写错误等。所以,在遇到错误的时候要先对细节部分进行检查,排除"低级错误"。
常见的异常和错误类型
# -------------------------------- 异常 ------------------------------------#
# -------------------------------- NameError ------------------------------------#
bar
# Traceback (most recent call last):
# File "/Users/apple/PycharmProjects/pythonProject3/异常处理.py", line 27, in <module>
# bar
# NameError: name 'bar' is not defined
# 在python中虽然不需要在使用变量之前先声明类型,但需要对变量进行赋值,然后才能使用。不被赋值的变量不能在python中存在,因为变量相当于一个标签,要把它贴到对象上才有意义。
# -------------------------------- 异常 ------------------------------------#
# -------------------------------- IndexError和KeyError ------------------------------------#
a = [1,2,3]
a[4]
# Traceback (most recent call last):
# File "/Users/apple/PycharmProjects/pythonProject3/异常处理.py", line 38, in <module>
# a[4]
# IndexError: list index out of range
d = {"python":"itdiffer.com"}
d["java"]
# Traceback (most recent call last):
# File "/Users/apple/PycharmProjects/pythonProject3/异常处理.py", line 45, in <module>
# d["java"]
# KeyError: 'java'
# 这两个都属于"鸡蛋里面挑骨头"的类型,一定得抛出异常。不过在编程实践中,特别是循环的时候,常常由于循环条件设置不合理而出现这种异常。
# -------------------------------- 异常 ------------------------------------#
# -------------------------------- IOError ------------------------------------#
f = open("foo")
# Traceback (most recent call last):
# File "/Users/apple/PycharmProjects/pythonProject3/异常处理.py", line 55, in <module>
# f = open("foo")
# IOError: [Errno 2] No such file or directory: 'foo'
# open()的作用是打开文件。如果找不到相应的文件,就会出现上述异常。
# 总结:这些都是python的内置异常,当然不局限于这几个,出现了异常不要慌张,这是好事情,是python在帮助修改和优化程序。只要认真阅读异常信息,再用dir(),help()或官方网站文档、Google等来协助,就一定能解决问题。
# -------------------------------- 异常处理 ------------------------------------#
# -------------------------------- 一般情况 ------------------------------------#
# 如果在程序运行过程中抛出异常,程序就会中止运行。这样的程序是不稳健的,稳健性强的程序应该是不为各种异常所击倒,所以要在程序中对异常进行处理。
# try...except...是常用的异常处理语句。
while True:
try:
n = int(input("Please enter integer:")) #1
print(n)
break
except:
print("Oo! Try again.")
# Please enter integer:a
# Oo! Try again.
# Please enter integer:2
# 2
# 这段代码先执行try分支下的代码块。如果用户输入的不是整数(如上述操作中输入了"a"),则在#1处发生了异常,就不再执行后面的语句,转而执行except分支下的语句(如上述操作,执行了print(),显示"Oo! Try again.")。因为上述代码是无限循环,所以再次执行了try分支,用户输入了2,没有发生异常,继续执行后面的语句,遇到break中止循环。
# 对于try...except...语句,try分支下是要执行的语句,except则是异常处理语句。上面示例没有规定except所处理的异常类型,在except后面可以申明异常类型。
# -------------------------------- 异常处理 ------------------------------------#
# -------------------------------- 申明异常类型 ------------------------------------#
class Calculator:
is_raise = False
def calc(self,express):
try:
return eval(express) #2
except ZeroDivisionError: #3
if self.is_raise:
return "zero can not be division." #4
else:
raise #5
if __name__=="__main__":
c = Calculator()
print(c.calc("8/0"))
# Traceback (most recent call last):
# File "/Users/apple/PycharmProjects/pythonProject3/异常处理.py", line 99, in <module>
# print(c.calc("8/0"))
# File "/Users/apple/PycharmProjects/pythonProject3/异常处理.py", line 91, in calc
# return eval(express) #2
# File "<string>", line 1, in <module>
# ZeroDivisionError: division by zero
#2 该处应用了一个函数eval,它能实现对字符串形式的表达式进行计算。比如:
# eval("3+5")6
# 8
# 这样,在调用方法calc的时候,给参数express提供的可以是一个字符串形式的表达式,由#2完成最终运算。
#3 此处的express分支明确了要处理的异常类型,其他异常类型这里就不处理了。在编程实践中,不提倡express后面不声明异常类型的做法,不要在一个处理异常的分支中包含太多异常处理,因为这样会让开发者一头雾水。
#4 当self.is_raise为真的时候,返回此处的异常信息。但是,这个类中已经规定了self.is_raise=False,所以默认状态下不会执行这个条件分支,而是执行else分支,即#5。
#5 以raise作为单独的一个语句,其含义是将异常信息抛出。再对照程序执行结果,所显示的错误信息就是raise执行结果。
# 如果将is_raise的值改为True,如下所示:
if __name__=="__main__":
c = Calculator()
c.is_raise = True
print(c.calc("8/0"))
# zero can not be division.
# 没有执行raise语句,是按照条件判断,执行了#4,这样可以控制显示异常处理中的提示信息内容。
# try...except...是处理异常的基本方式。在此基础上还可以扩展,能够处理多个异常。
# -------------------------------- 异常处理 ------------------------------------#
# -------------------------------- 多个异常 ------------------------------------#
# 处理多个异常并不是因为同时报出多个异常。程序在运行中,只要遇到一个异常就会有反应,所以每次捕获到的异常一定是一个。所谓处理多个异常,是可以允许捕获不同的异常,由不同的except子句处理。
while True:
try:
a = float(input("first number:"))
b = float(input("second number:"))
r = a / b
print("{}/{}={}".format(a,b,r))
break
except ZeroDivisionError:
print("The second number can not be zero.Try again.")
except ValueError:
print("Please enter number.Try again.")
except:
break
# first number:6
# second number:0
# The second number can not be zero.Try again.
# first number:6
# second number:a
# Please enter number.Try again.
# first number:6
# second number:4
# 6.0/4.0=1.5
# except后面不仅可以放一个异常类型的名称,还可以放多个。比如上面的示例程序,可以修改为:
while True:
try:
a = float(input("first number:"))
b = float(input("second number:"))
r = a / b
print("{}/{}={}".format(a,b,r))
break
except (ZeroDivisionError,ValueError):
print("Try again.") #6
# except ZeroDivisionError:
# print("The second number can not be zero.Try again.")
# except ValueError:
# print("Please enter number.Try again.")
except:
break
# first number:6
# second number:0
# Try again.
# first number:6
# second number:a
# Try again.
# first number:6
# second number:4
# 6.0/4.0=1.5
# 请把此处的执行过程和结果与前述进行对比,理解except后面多个参数的作用。注意except后面的多个参数,一定要用圆括号包括起来。
#6 当except处理异常的时候,此处即为显示信息。如果觉得这种显示信息不友好,还可以继续修改except子句,将异常中原有的提示信息打印出来。
while True:
try:
a = float(input("first number:"))
b = float(input("second number:"))
r = a / b
print("{}/{}={}".format(a,b,r))
break
except (ZeroDivisionError,ValueError) as e: #7
print(e)
print("Try again.")
except:
break
# first number:6
# second number:0
# float division by zero
# Try again.
# first number:6
# second number:a
# could not convert string to float: 'a'
# Try again.
# first number:6
# second number:4
# 6.0/4.0=1.5
#7 此处的e就引用了每次异常时的默认提示信息。
# -------------------------------- 异常处理 ------------------------------------#
# -------------------------------- else分支 ------------------------------------#
# try...except...还可以有一个可选的else分支,通常放在所有except的后面,当没有异常的时候,执行该分支下的语句块。
try:
print("I am in try.")
except:
print("I am in except.")
else:
print("I am in else.")
# I am in try.
# I am in else.
# 此处,except没有捕获异常,执行的是try和else两个分支,再对比如下示例:
try:
print(1/0)
except:
print("I am in except.")
else:
print("I am in else.")
# I am in except.
# 这段代码中有异常需要except处理,这时else分支就不被执行了。
# 下面使用else语句对#7所在的程序进行优化。
while True:
try:
a = float(input("first number:"))
b = float(input("second number:"))
r = a / b
print("{}/{}={}".format(a,b,r))
# break #8
except (ZeroDivisionError,ValueError) as e:
print(e)
print("Try again.")
else:
break
# first number:6
# second number:0
# float division by zero
# Try again.
# first number:6
# second number:a
# could not convert string to float: 'a'
# Try again.
# first number:6
# second number:4
# 6.0/4.0=1.5
#8 此处的break注释了,但是因为增加了else语句,程序没有陷入"死循环"。
# -------------------------------- 异常处理 ------------------------------------#
# -------------------------------- finally分支 ------------------------------------#
# 如果说else是try的跟随者,那么另一个名为finally的分支就是"终结者"了。它的作用是不管前面执行哪个分支,最后都要执行它。
try:
print("I am in try.")
except:
print("I am in except.")
else:
print("I am in else.")
finally:
print("I am in finally.")
# I am in try.
# I am in else.
# I am in finally.
try:
print(1/0)
except:
print("I am in except.")
else:
print("I am in else.")
finally:
print("I am in finally.")
# I am in except.
# I am in finally.
# -------------------------------- 异常处理 ------------------------------------#
# -------------------------------- raise语句 ------------------------------------#
# 除了使用try...except...处理异常,有时需要主动抛出异常。raise语句就是完成这个工作的。
def enterage(age):
if age < 0:
raise ValueError("Only positive integers are allowed") #9
if age % 2 == 0:
print("age is even")
else:
print("age is odd")
try:
num = int(input("Enter your age:"))
enterage(num)
except ValueError:
print("Only integers are allowed")
except:
print("something is wrong")
# Enter your age:python #10
# Only integers are allowed
# Enter your age:11
# age is odd
# Enter your age:12
# age is even
# Enter your age:-22 #11
# Only integers are allowed
# 调试程序的时候,如果输入了字符串(如#10),这个异常会被except ValueError捕获;如果输入了负数(如#11),则#9会主动抛出异常。
# -------------------------------- 异常处理 ------------------------------------#
# -------------------------------- assert语句 ------------------------------------#
def year(x):
assert x > 0 #12
return str(x) + "year"
print(year(2018))
# 2018year
print(year(-2018))
# Traceback (most recent call last):
# File "/Users/apple/PycharmProjects/pythonProject3/异常处理.py", line 316, in <module>
# print(year(-2018))
# File "/Users/apple/PycharmProjects/pythonProject3/异常处理.py", line 312, in year
# assert x > 0
# AssertionError
# 在函数year的#12使用了关键字assert发起的语句。中文将assert译为"断言",它发起的语句等价于条件判断,发生异常就意味着表达式为假。
# -------------------------------- 自定义异常类型 ------------------------------------#
# 各种异常其实也是对象,请以自定义对象类型来修改函数enterage。