Python异常处理

一、什么是异常

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

在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:索引超出列表的限制
lis = ['jason', 'tony']
lis[3]
 
# KeyError:引用了一个不存在的key
dic = {'name': 'jason'}
dic['age']
 
# AttributeError:引用的属性不存在
class Foo:
    pass

Foo.x
 
# ZeroDivisionError:除数不能为0
1 / 0

Python异常处理

二、异常处理

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

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

举例如下:

try:
    print('start...')
    print(x)  # 引用了一个不存在的名字,触发异常NameError
    print('end...')
except NameError as e:
    print('异常值为:%s' % e)
print('run other code...')

"""as语法将异常类型的值赋值给变量e,这样我们通过打印e便可以知道错误的原因"""

执行结果为:

start...
异常值为:name 'x' is not defined
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

convert_int('jason')  # ValueError: invalid literal for int() with base 10: 'jason'
convert_int({'n': 1})  # TypeError: int() argument must be a string, a bytes-like object or a number, not 'dict'

如果我们想多种类型的异常统一用一种逻辑处理,可以将多个异常放到一个元组内,用一个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

convert_int('eason')  # argument must be number or numeric string
convert_int({'n': 1})  # argument must be number or numeric string

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

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

Python异常处理

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

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

此外try还可以与finally连用,从语法上讲finally必须放到else之后,但可以使用try-except-finally的形式,也可以直接使用try-finally的形式;

无论被检测的代码块是否触发异常,都会执行finally的子代码块,因此通常在finally的子代码块做一些回收资源的操作,比如关闭打开的文件、关闭数据库连接等。

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

举例如下:

f = None
try:
    f = open(‘db.txt’, 'r', encoding='utf-8')
    s = f.read().strip()
    int(s)  # 若字符串s中包含非数字时则会触发异常ValueError
    f.close()  """若上面的代码触发异常,则根本不可能执行到此处的代码,应该将关闭文件的操作放到finally中"""
finally:
    if f:  # 文件存在则f的值不为None
        f.close()

在不符合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

stu1 = Student(4573, 18)  # TypeError: name must be str
stu2 = Student('jason', '18')  # TypeError: age must be int

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

可以通过继承Exception来定义一个全新的异常:

class PoolEmptyError(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


raise PoolEmptyError  # __main__.PoolEmptyError: < The proxy source is exhausted >
raise NetworkIOError('连接被拒绝')  # __main__.NetworkIOError: 连接被拒绝

最后,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

Python异常处理

三、何时使用异常处理

在了解了异常处理机制后,本着提高程序容错性和可靠性的目的,读者可能会错误地认为应该尽可能多地为程序加上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

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异常处理

上一篇:爬虫学习笔记2-异常的处理


下一篇:异常捕获处理