python基础(补充):异常处理

什么是异常

异常是程序发生错误的信号。程序一旦出现错误,便会产生一个异常,若程序中没有处理它,就会抛出该异常,程序的运行也随之终止。在Python中,错误触发的异常如下

而错误分成两种,一种是语法上的错误SyntaxError,这种错误应该在程序运行前就修改正确

>>> if  
  File "<stdin>", line 1
    if
     ^
SyntaxError: invalid syntax

另一类就是逻辑错误,常见的逻辑错误如

# TypeError:数字类型无法与字符串类型相加
1+’2’

# ValueError:当字符串包含有非数字的值时,无法转成int类型
num=input(">>: ") #输入hello
int(num)

# NameError:引用了一个不存在的名字x
x

# IndexError:索引超出列表的限制
l=['egon','aa']
l[3]

# KeyError:引用了一个不存在的key
dic={'name':'egon'}
dic['age']

# AttributeError:引用的属性不存在
class Foo:
    pass
Foo.x

# ZeroDivisionError:除数不能为0
1/0

异常处理

为了保证程序的容错性与可靠性,即在遇到错误时有相应的处理机制不会任由程序崩溃掉,我们需要对异常进行处理,处理的基本形式为

try/except

try:
    被检测的代码块
except 异常类型:
    检测到异常,就执行这个位置的逻辑

举例

try:
    print('start...')
    print(x) # 引用了一个不存在的名字,触发异常NameError
    print('end...')
except NameError as e: # as语法将异常类型的值赋值给变量e,这样我们通过打印e便可以知道错误的原因
    print('异常值为:%s' %e)
print('run other code...')

执行结果为

本来程序一旦出现异常就整体结束掉了,有了异常处理以后,在被检测的代码块出现异常时,被检测的代码块中异常发生位置之后的代码将不会执行,取而代之的是执行匹配异常的except子代码块,其余代码均正常运行。

当被检测的代码块中有可能触发不同类型的异常时,针对不同类型的异常:

如果我们想分别用不同的逻辑处理,需要用到多分支的except(类似于多分支的elif,从上到下依次匹配,匹配成功一次便不再匹配其他)

try:
    被检测的代码块
except NameError:
    触发NameError时对应的处理逻辑
except IndexError:
    触发IndexError时对应的处理逻辑
except KeyError:
    触发KeyError时对应的处理逻辑

举例

def convert_int(obj):
    try:
        res=int(obj)
    except ValueError as e:
        print('ValueError: %s' %e)
        res=None
    except TypeError as e:
        print('TypeError: %s' %e)
        res=None
    return res

执行结果为

如果我们想多种类型的异常统一用一种逻辑处理,可以将多个异常放到一个元组内,用一个except匹配

try:
    被检测的代码块
except (NameError,IndexError,TypeError):
    触发NameError或IndexError或TypeError时对应的处理逻辑

举例

def convert_int(obj):
    try:
        res=int(obj)
    except (ValueError,TypeError):
        print('argument must be number or numeric string')
        res=None
    return res

执行结果为

如果我们想捕获所有异常并用一种逻辑处理,Python提供了一个万能异常类型Exception

try:
    被检测的代码块
except NameError:
    触发NameError时对应的处理逻辑
except IndexError:
    触发IndexError时对应的处理逻辑
except Exception:
    其他类型的异常统一用此处的逻辑处理

try/except...else

在多分支except之后还可以跟一个else(else必须跟在except之后,不能单独存在),只有在被检测的代码块没有触发任何异常的情况下才会执行else的子代码块

try:
    被检测的代码块
except 异常类型1:
    pass
except 异常类型2:
    pass
......
else:
    没有异常发生时执行的代码块

举例

import sys
for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except IOError:
        print('cannot open', arg)
    else:
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()

执行结果为

try-finally

try还可以与finally连用,从语法上讲finally必须放到else之后,但可以使用try-except-finally的形式,也可以直接使用try-finally的形式。无论被检测的代码块是否触发异常,都会执行finally的子代码块,因此通常在finally的子代码块做一些回收资源的操作,比如关闭打开的文件、关闭数据库连接等

python try:
    被检测的代码块
except 异常类型1:
    pass
except 异常类型2:
    pass ......
else:
    没有异常发生时执行的代码块 
finally:
    无论有无异常发生都会执行的代码块

举例

try:
    print('start...')
    print(x) # 引用了一个不存在的名字,触发异常NameError

finally:
    print('这句话,无论异常是否发生都会执行。')

执行结果为

抛出异常

在不符合Python解释器的语法或逻辑规则时,是由Python解释器主动触发的各种类型的异常,而对于违反程序员自定制的各类规则,则需要由程序员自己来明确地触发异常,这就用到了raise语句,raise后必须是一个异常的类或者是异常的实例

class Student:
    def __init__(self,name,age):
        if not isinstance(name,str):
            raise TypeError('name must be str')
        if not isinstance(age,int):
            raise TypeError('age must be int')

        self.name=name
        self.age=age

执行结果为

用户自定义异常

在内置异常不够用的情况下,我们可以通过继承内置的异常类来自定义异常类

class PoolEmptyError(Exception): # 可以通过继承Exception来定义一个全新的异常
    def __init__(self,value='The proxy source is exhausted'): # 可以定制初始化方法
        super(PoolEmptyError,self).__init__()
        self.value=value

    def __str__(self): # 可以定义该方法用来定制触发异常时打印异常值的格式
        return '< %s >' %self.value


class NetworkIOError(IOError): # 也可以在特定异常的基础上扩展一个相关的异常
    pass

执行结果为

断言语句

Python还提供了一个断言语句assert expression,断定表达式expression成立,否则触发异常AssertionError,与raise-if-not的语义相同,如下

age='18'

# 若表达式isinstance(age,int)返回值为False则触发异常AssertionError
assert isinstance(age,int)

# 等同于
if not isinstance(age,int):
    raise AssertionError

何时使用异常处理

在了解了异常处理机制后,本着提高程序容错性和可靠性的目的,读者可能会错误地认为应该尽可能多地为程序加上try...except...,这其是在过度消费程序的可读性,因为try...except本来就是你附加给程序的一种额外的逻辑,与你的主要工作是没有多大关系的。

如果错误发生的条件是“可预知的”,我们应该用if来进行”预防”,如下

age=input('input your age>>: ').strip()
if age.isdigit(): # 可预知只有满足字符串age是数字的条件,int(age)才不会触发异常,
    age=int(age)
else:
    print('You must enter the number')

如果错误发生的条件“不可预知”,即异常一定会触发,那么我们才应该使用try...except语句来处理。例如我们编写一个下载网页内容的功能,网络发生延迟之类的异常是很正常的事,而我们根本无法预知在满足什么条件的情况下才会出现延迟,因而只能用异常处理机制了

import requests
from requests.exceptions import ConnectTimeout # 导入requests模块内自定义的异常

def get(url):
    try:
        response=requests.get(url,timeout=3)#超过3秒未下载成功则触发ConnectTimeout异常
        res=response.text
    except ConnectTimeout:
        print('连接请求超时')
        res=None
    except Exception:
        print('网络出现其他异常')
        res=None
    return res

get('https://www.python.org')

python标准异常

异常名称 描述
BaseException 所有异常的基类
SystemExit 解释器请求退出
KeyboardInterrupt 用户中断执行(通常是输入^C)
Exception 常规错误的基类
StopIteration 迭代器没有更多的值
GeneratorExit 生成器(generator)发生异常来通知退出
StandardError 所有的内建标准异常的基类
ArithmeticError 所有数值计算错误的基类
FloatingPointError 浮点计算错误
OverflowError 数值运算超出最大限制
ZeroDivisionError 除(或取模)零 (所有数据类型)
AssertionError 断言语句失败
AttributeError 对象没有这个属性
EOFError 没有内建输入,到达EOF 标记
EnvironmentError 操作系统错误的基类
IOError 输入/输出操作失败
OSError 操作系统错误
WindowsError 系统调用失败
ImportError 导入模块/对象失败
LookupError 无效数据查询的基类
IndexError 序列中没有此索引(index)
KeyError 映射中没有这个键
MemoryError 内存溢出错误(对于Python 解释器不是致命的)
NameError 未声明/初始化对象 (没有属性)
UnboundLocalError 访问未初始化的本地变量
ReferenceError 弱引用(Weak reference)试图访问已经垃圾回收了的对象
RuntimeError 一般的运行时错误
NotImplementedError 尚未实现的方法
SyntaxError Python 语法错误
IndentationError 缩进错误
TabError Tab 和空格混用
SystemError 一般的解释器系统错误
TypeError 对类型无效的操作
ValueError 传入无效的参数
UnicodeError Unicode 相关的错误
UnicodeDecodeError Unicode 解码时的错误
UnicodeEncodeError Unicode 编码时错误
UnicodeTranslateError Unicode 转换时错误
Warning 警告的基类
DeprecationWarning 关于被弃用的特征的警告
FutureWarning 关于构造将来语义会有改变的警告
OverflowWarning 旧的关于自动提升为长整型(long)的警告
PendingDeprecationWarning 关于特性将会被废弃的警告
RuntimeWarning 可疑的运行时行为(runtime behavior)的警告
SyntaxWarning 可疑的语法的警告
UserWarning 用户代码生成的警告
posted @ 2021-05-16 17:59  pure3417  阅读(99)  评论(0编辑  收藏  举报