8.Python 异常 (Exception)

15 阅读5分钟

1. Python 中的错误

在 Python 开发过程中,可能会遇到 3 种错误:

  1. 语法错误 (Syntax Error):代码不符合 Python 语法规则。
    • PyCharm 会给出红色的波浪线提示,运行会出现 SyntaxError
    # 计算 a 跟 b 的和
    a = 10
    b = 5
    print(a - b)  # 5 
    
  2. 逻辑错误 (Logic Error、Bug):代码不符合业务逻辑。
    • PyCharm 不会给出任何提示信息。
  3. 异常 (Exception):操作不合理、Python 无法正常处理你的代码。
    • Python 会抛出异常信息(比如 XxxError)。
    i = int('小码哥')
    print(i)
    
    print(10 / 0)
    

2. 异常的特点

  • 会直接导致程序终止运行
print(1)
print(10 / 0)
print(2)

def test():
    print(2)
    print(10 / 0)
    print(3)

print(1)
test()
print(4)

# 运行结果:
# 1
# ZeroDivisionError: division by zero
# 1
# 2
# ZeroDivisionError: division by zero
  • 有时候来得猝不及防,把握不住
age = int(input('请输入你的年龄:'))
print(age)

3. 异常的处理

在绝大部分情况下:

  • 我们都不希望程序因为异常而终止运行。
  • 我们更希望的是:
    • 能够“把握住”异常
    • 能够拦截(捕获)异常
    • 根据不同的异常信息作出不同的处理
    • 让程序保持正常运行

3.1 捕获异常的基本结构

tryexceptelsefinally 配合使用:

  • try:包含可能会产生异常的代码。
    • 一旦出现异常,会直接跳过 try 中剩余未执行的代码。
  • except:当出现异常时,会执行 except 中的代码。
    • 可以省略。
  • else:当没有出现异常时,会执行 else 中的代码。
    • 可以省略。只能在有 except 时使用
  • finally:不管是否出现异常,最终都会执行 finally 中的代码。
    • 可以省略。
try:
    代码1
except:
    代码2
else:
    代码3
finally:
    代码4

image.png

综合示例:

try:
    a = int(input('请输入第1个整数:'))
    print(1)
    b = int(input('请输入第2个整数:'))
    print(2)
    print(f'a除以b等于{a / b}')
    print(3)
except ValueError:
    print('输入的数据无法转成整数')
except ZeroDivisionError:
    print('0不能作为被除数')
else:
    print('很好,一切正常')
finally:
    print('操作处理完毕')
print(4)

3.2 常见的内置异常类型

  1. ZeroDivisionError:0 作为除数
    print(10 / 0) # ZeroDivisionError: division by zero
    
  2. IndexError:索引超出正常范围
    s = [11, 22, 33]
    print(s[5]) # IndexError: list index out of range
    
  3. NameError:无法找到名称(标识符)
    print(age) # NameError: name 'age' is not defined
    
  4. KeyError:无法找到字典的 key
    d = {'age': 18}
    print(d['name']) # KeyError: 'name'
    
  5. TypeError:使用了不恰当类型的对象
    print('age is ' + 18) # TypeError: can only concatenate str (not "int") to str
    
  6. ValueError:虽然类型正确,但值不正确
    n = int('娃哈哈') # ValueError: invalid literal for int() with base 10: '娃哈哈'
    
  7. AttributeError:操作(访问、赋值等)属性失败
    class Person:
        pass
        
    p = Person()
    print(p.age) # AttributeError: 'Person' object has no attribute 'age'
    

3.3 except 的高级用法

  • 省略异常类型except 后面可以不写异常类型,表示支持所有的异常类型。
try:
    a = int(input('请输入第1个整数:'))
    b = int(input('请输入第2个整数:'))
    print(f'a除以b等于{a / b}')
    print([][10])
except ValueError:
    print('输入的数据无法转成整数')
except ZeroDivisionError:
    print('0不能作为被除数')
except:
    print('其他异常')
  • 捕获多个异常类型:可以用元组同时表示多个异常类型。
try:
    a = int(input('请输入第1个整数:'))
    b = int(input('请输入第2个整数:'))
    print(f'a除以b等于{a / b}')
except (ValueError, ZeroDivisionError):
    print('出现异常')
  • 接收异常对象:可以用变量来接收异常对象(使用关键字 as)。
try:
    n = int(input('请输入第1个整数:'))
    print(n)
except BaseException as e:
    print('出现异常', e)

try:
    a = int(input('请输入第1个整数:'))
    b = int(input('请输入第2个整数:'))
    print(f'a除以b等于{a / b}')
except (ValueError, ZeroDivisionError) as e:
    print('出现异常', e)

4. 异常的传播

  • 一旦函数/方法中的代码出现了异常:
    • 会直接跳过剩余的代码,函数/方法停止运行。
    • 函数/方法中产生的异常,会向外传播到当初调用函数/方法的地方。
  • 从异常的打印信息(Traceback),可以很清晰地看出异常的传播轨迹。
  • 异常会由内往外传播出去,直到被拦截捕获或传播到全局作用域为止。
    • 可以想象成是在寻求帮助,看看哪位大神能够收了(处理)这个异常。
    • 当异常传播到全局作用域时,还没有被拦截捕获,就会导致程序终止运行,并将异常信息打印出来。

5. 抛出异常 (raise)

如果函数/方法的参数有问题,我们可以在函数/方法内部:

  1. 将不合理的值过滤掉。
  2. 抛出异常,告诉传递参数的人:你传递的参数有严重问题。

可以通过 raise 主动抛出异常:

  • raise 后面跟上的必须是异常类型(BaseException 类型或其子类类型)。
class Person:
    def __init__(self):
        self.__age = 1
        
    @property
    def age(self):
        return self.__age
        
    @age.setter
    def age(self, age):
        if age < 1:  # 主动抛出异常
            raise ValueError('age不能小于1')
        if age > 120:  # 主动抛出异常
            raise ValueError('age不能大于120')
        self.__age = age

try:
    p = Person()
    p.age = -10
    print(p.age)
except ValueError as e:
    print('出现了异常', e.args[0])

6. 自定义异常

为了让异常的类型、描述信息更加精准,可以考虑自定义异常。

  • 一般建议:自定义的异常类型,最终要继承自 Exception,而并不是 BaseException
class AgeError(Exception):
    """自定义的异常类型,表示age有问题"""
    def __str__(self):
        return f'{self.args[0]}{self.args[1]}值不合理'

class Person:
    def __init__(self):
        self.__age = 1
        
    @property
    def age(self):
        return self.__age
        
    @age.setter
    def age(self, age):
        if age < 1:  # 主动抛出异常
            raise AgeError('age不能小于1', age)
        if age > 120:  # 主动抛出异常
            raise AgeError('age不能大于120', age)
        self.__age = age

try:
    p = Person()
    p.age = 300
    print(p.age)
except AgeError as e:
    print('出现了异常', e)