Python 极简教程(二十三) - 异常处理

异常,什么是异常呢?

有没有见过在浏览网页出现 500 错误?!

500错误

图片来自百度,侵删!

如果没见过,那肯定在学习 Python 过程中见过如下情景:


异常

不管是浏览网页时出现的 500 错误,还是在写代码过程中的 “报红”,这些都是代码在运行过程中出现了异常。

这些错误如果不处理会出现什么样的情况呢?当前运行的代码会中断。

print('start')
print(name)   # 注意,这里的 name 没有引号
print('end')

上图中的 name 没有打引号,会被 Python 认为是一个变量,而如果这个变量没有定义,那么会出现下面的错误:

错误

我们可以看到 start成功的打印了出来,但是 end却没有,这是因为 第二句print(name)抛出了异常。

那么会有什么影响呢?假如,我们是在操作文件或数据库的过程中出现了异常:

# 伪代码,只是为了说明操作文件和数据库的过程
# 操作文件过程
f = open('nemo.txt') # 打开文件
f.read()   # 读取文件内容
f.close()  # 关闭文件

# 操作数据库过程
db = connect()  # 连接数据库
db.options  # 数据库操作过程
db.close()  # 断开数连接

如果在这样的处理过程中,操作文件的过程或者操作数据库的过程中报错了,会导致后续的文件关闭和数据库连接无法正常断开。这就会造成内存驻留和数据库的连接被占用。

通常,为了保证关闭操作能够正常进行,我们都需要处理操作过程中可能出现的异常。当然还有很多其他的用处!

在 Python 中,处理异常的语句为try...except...finally(else)...

具体语法如下:

try:
  可能出现异常的语句块

except 错误类型:
  如果出现错误后,执行的代码

else:
  没有出现错误,执行这里的代码

finally:
  不管有没有出现错误,这里的代码都会执行

举例来说。假设我们现在想写一个函数,用来进行出发运算,但是除数不能为0,为0就会报一个异常ZeroDivisionError,当用户不小心把被除数数为0,我们不能直接给用户一个异常扔脸上吧!所以我们至少要处理一下异常,并告诉用户你输错了。

def division(a, b):
  '''a 为被除数,b 为除数'''
  try:
    return a/b
  except ZeroDivisionError:
    print('除数不能为 0 !')

>>> division(10,2)
5.0
>>> division(5,0)
除数不能为 0 !

但是用户除了可能会乱输 0 以外,还可能输一些命名其妙的符号。

>>> division(10,'$@]')
...异常堆栈信息省略
TypeError: unsupported operand type(s) for /: 'int' and 'str'

咦,不是做了异常处理么?咋还要报错呢?

这回错误不一样了,是TypeError

回头看看我们写的函数,我们只处理了ZeroDivisionError这种异常,而对于非数字类型的错误没有处理。那要处理怎么办呢?

针对上面报的TypeError ,再加一个 except 处理:

def division(a, b):
  '''a 为除数,b 为被除数'''
  try:
    return a/b
  except ZeroDivisionError:
    print('被除数不能为 0 !')
  except TypeError:
    print('除法只支持数值类型!')

>>> division(10,'$@]')
除法只支持数值类型!

好了,这种异常也处理了。可能有人会问,有没有能一次性处理所有异常的方式呢?

当然有,就是使用所有异常类型(每种异常类型是 Python 中的一个异常类)的父类Exception

def division(a, b):
  '''a 为被除数,b 为除数'''
  try:
    return a/b
  except Exception:
    print('你的输入有误!')

>>> division(10,'$@]')
你的输入有误!
>>> division(5,0)
你的输入有误!

我们会发现,虽然这种方式能处理所有异常,但是没有办法针对不同的情况给予用户合适的提示。所以根据情况选用!

接下来,我们说一下finallyelse子句。

先说 else,我们改造一下除法函数,当输入正确的情况下,我们告诉用户除法的结果。

def division(a, b):
  '''a 为被除数,b 为除数'''
  try:
    r = a/b
  except ZeroDivisionError:
    print('除数不能为 0 !')
  except TypeError:
    print('除法只支持数值类型!')
  else:
    print(f'{a} 除以 {b} 的结果是 {r}')

>>> division(10,'$@]')
除法只支持数值类型!
>>> division(10, 2)
10 除以 2 的结果是 5.0

在某些情况下,可以使用 try...except...else...代替if...else...来使用。

最后就是 finally 子句了,finally 是无论如何都会执行的。我们看下面的例子

a = input('请输入一个数:')
try:
    a = int(a)
    b = 100 / a
except ValueError:
    print('请输入整数!')
print('end')

当某种异常没有被捕获到的时候,我们发现后面的 print('end')并没有执行。如果我们想无论如何都执行 print('end'),就可以使用 finally。

a = input('请输入一个数:')
try:
    a = int(a)
    b = 100 / a
except ValueError:
    print('请输入整数!')
finally:
    print('end')

请输入一个数:0
end
...异常堆栈省略
ZeroDivisionError: division by zero

我们可以看到 end 也会打印出来,异常也会抛出来。

那么对于文件操作就可以将 close() 关闭语句放在 finally 中,以保证无论什么情况下文件都能被正常关闭。

f = open('nemo.txt')
try:
  f.read()
except Exception as e:  # as e,把异常提示保存在 e 变量中
  print(e)  # 打印异常的提示,如 ''ZeroDivisionError: division by zero''
finally:
  f.close()  # 放在 finally 语句中,无论操作过程报什么错,都会正常关闭文件

你可能感兴趣的:(Python 极简教程(二十三) - 异常处理)