python的异常处理

时间:2021-10-18 00:47:02

1、问题

无论开发大型程序还是自动化开发者自己的日常杂事,不可避免的会碰到bug、以前没有考虑的问题、系统故障等等问题。这时需要程序尽可能保持稳定,完成基本清理,提供清晰的问题栈线索和运行日志,便于分析查错。python的异常处理就提供这样一个机制。

2、处理流程

python的异常处理的基本语法如下:

try:
    <正常运行的程序逻辑>
except <异常类型1>:
    <异常类型1处理>
except <异常类型...>:
    <异常类型...处理>
except <异常类型N>:
    <异常类型N处理>
except:
    <未知类型类型处理>
else:
    <除异常外,还需要运行的程序逻辑>
finally:
    <无论正常还是异常都需要的最后处理和清理>

python系统提供了一些基本的异常类型,每一个python包一般也会提供相应的定制异常,开发者也可以自己定义异常类型。

在python程序中也可以产生异常:

>>> raise NameError('true')
Traceback (most recent call last):
  File "<pyshell#17>", line 1, in <module>
    raise NameError('true')
NameError: true

2.1、语法异常:SyntaxError和NameError

运行python代码的时候,语法异常是常遇到的问题。

有时候是使用的关键词不对,或是变量未定义,系统会报告NameError。比如下例中,true不是python的关键词True,也不是定义的变量:

>>> while true:
    print (0/0)
  
Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    while true:
NameError: name 'true' is not defined

有时候是语法问题,系统会报告SyntaxError:

>>> if True
SyntaxError: invalid syntax

这个例子中,缺少条件语句行需要的“:”号结尾符。

2.2、基本系统异常类型

python系统提供了一些基本的异常类型,下面是一些典型的内置系统异常:

  • AssertError: assert语句出错
  • EOFError:输入input()遇到文件末尾
  • FloatingPointError:浮点数错误
  • ImportError/ModuleNotFoundError:装载包出错
  • IndexError:索引出界
  • KeyError:字典不存在要查询的键,可以用in关键词来检验
  • NameError:无法找到局部或全局变量
  • NotImplementedError:调用没有定义方法,是RuntimeError的子类
  • OSError:系统调用出错,比如I/O,或是无硬盘空间了
  • OverflowError:数值计算溢出
  • RuntimeError:运行出错
  • SystemError:
  • TypeError:类型错误
  • UnicodeError:unicode编码和译码错误
  • ValueError:数值类型不匹配
  • ZeroDivisionError:除数是0

所有python系统内置的异常可以在下面文档中获得:

[python3]:https://docs.python.org/3/library/exceptions.html#bltin-exceptions

[python2]:https://docs.python.org/2/library/exceptions.html#bltin-exceptions

 2.3、定制异常类型

python程序中可以定义自己异常,这样程序的使用者可以针对这种异常作特殊的处理。比如,网络连接出错可以重试。

class Error(Exception):
    """定义本模块的出错基类."""
    pass

class InputError(Error):
    """输入出错.

    属性:
        expression -- 出错的输入表达式
        message -- 出错的信息
    """
    def __init__(self, expression, message):
        self.expression = expression
        self.message = message

class StateTransitionError(Error):
    """系统执行了错误的状态转移.

    属性:
        previous -- 初始状态
        next -- 未来状态
        message -- 状态转移出错信息
    """
    def __init__(self, previous, next, message):
        self.previous = previous
        self.next = next
        self.message = message

2.4、获取系统出错信息

python系统或其他软件包提供的异常类型都会提供出错信息。但是有时候还不够,比如,我们需要得到系统的访问栈,需要获得一些系统运行状态的信息。

我们可以使用traceback包获得访问栈的信息:

import traceback

def get_calltrace():
    try:
        a = 1
        b = 0
        c = a/b
    except:
        tb = traceback.format_exc()
    else:
        tb = "No error"
    finally:
        print (tb)

>>> get_calltrace()
Traceback (most recent call last):
  File "<pyshell#28>", line 5, in get_calltrace
ZeroDivisionError: division by zero

 有时候,需要从系统获取更多信息,可以调用sys.exc_info() :

import sys, traceback

def
get_calltrace(): try: a = 1 b = 0 c = a/b except: tb = traceback.format_exc() exc= sys.exc_info() else: tb = "No error" exc = None finally: print (tb) if exc: print (exc[0])      print (exc[1]) print (exc[2])

3、assert和参数检查

 当我们设计程序的时候,常常需要考虑如何让系统更加稳定,帮助开发者尽早发现问题,让最终客户拥有最棒的使用体验。

  • assert:主要为内部开发使用,用于验证假设,帮助程序设计尽早发现问题。
  • 参数检查:主要为外部API开发使用,用于验证输入参数,帮助程序设计尽早发现问题;调用程序必须检查返回值是否出错。

3.1、assert

不加检验的函数总会出错,而且出错信息无法控制:

def add(a, b):
    return a + b

>>> add('hello', 7)
Traceback (most recent call last):
  File "<pyshell#45>", line 1, in <module>
    add('hello', 7)
  File "<pyshell#44>", line 2, in add
    return a + b
TypeError: must be str, not int

我们可以用assert来验证调用输入的有效性,并提供清晰提示:

 def add_assert(a, b):
    assert isinstance(a, (int, float)), "a isn't numeric"
    assert isinstance(b, (int, float)), "b isn't numeric"
    return a + b

>>> add_assert('hello', 7)
Traceback (most recent call last):
  File "<pyshell#69>", line 1, in <module>
    add_assert('hello', 7)
  File "<pyshell#68>", line 2, in add_assert
    assert isinstance(a, (int, float)), "a isn't numeric"
AssertionError: a isn't numeric

3.2、参数检查

如果某个函数是开放的API,需要做输入检查,并返回出错代码提示调用者:

def add_validate(a, b):
    if not isinstance(a, (int, float)):
        return ("Fail", "a isn't numeric")
    if not isinstance(b, (int, float)):
        return ("Fail", "b isn't numeric")
    return ("OK", a+b)

>>> add_validate('hello', 7)
('Fail', "a isn't numeric")
>>> add_validate(5.0, 7)
('OK', 12.0)

4、还需注意的问题

 异常总会发生,对未知异常在程序中必须捕作并处理,否则会导致系统崩溃。

在捕捉异常的时候,需要先捕捉特殊的异常,最后捕捉普通的,未知的异常。

记得在finally语句里释放分配的资源。虽然python有垃圾回收处理逻辑,不及时地释放资源是python程序出现内存泄露的主因。