0x01:异常

程序在运行过程中,由于我们的编码不规范,或者其他原因一些客观原因,导致我们的程序无法继续运行,此时,程序就会出现异常。如果我们不对异常进行处理,程序可能会由于异常直接中断掉。为了保证程序的健壮性,我们在程序设计里提出了异常处理这个概念。

异常: Python 使用异常对象来代表异常状态,并在遇到错误时引发异常。异常对象未被处理(或捕获时),程序将终止并显示一条错误信息(traceback)。

  1. >>> 1 / 0
  2. Traceback (most recent call last):
  3. File "<pyshell#0>", line 1, in <module>
  4. 1 / 0
  5. ZeroDivisionError: division by zero
  6. >>>

如果异常只能用来显示错误信息,就没有什么意思了。每个异常都是某个类(这里是ZeroDivisionError)的实例。你能以各种方式引发和捕获这些实例,从而逮住错误并采取措施。

0x02: 一些内置的异常类

Exception:        几乎所有的异常类都是它派生出来的
AttributeError:   引用属性或给它赋值失败时引发
OSError:                    操作系统不能执行指定的任务(如打开文件)时引发,有多个子类
IndexError:                使用序列中不存在的索引时引发,为LookupError的子类
KeyError:                    使用映射中不存在的键时引发,为LookupError的子类
NameError:                找不到名称(变量)时引发
SyntaxError:            代码不正确时引发
TypeError:                  将内置操作或函数用于类型不正确的对象时引发
ValueError:                将内置操作或函数用于这样的对象时引发:其类型正确,但是包含的值不正确
ZeroDivisionError:在除法或求模运算的第二个参数为零时引发

创建自定义的异常类

class SomeCustomException(Exception): pass

0x03: 捕获异常

异常比较有趣的地方是可以对其进行处理,通常称之为捕获异常。可以使用 try / except 语句。

try:
    可能会出现异常的代码块
except 异常的类型:
    出现异常以后的处理语句

示例:

>>> try:
    x = int(input('Enter  the first number: '))
    y = int(input('Enter  the second  number: '))
    print(x / y)
except ZeroDivisionError:
    print(" The second number can't be zero")

Enter  the first number: 1
Enter  the second  number: 0
 The second number can't be zero
>>>

(1) 不用提供参数:

捕获异常后,如果要重新引发它(即继续向上传播),可以调用 raise 且不提供任何参数(也可以显示的提供捕获到的异常)。

接下来看一个能够“抑制” 异常 ZeroDivisionError的计算器类。如果启动了这种功能,计算器将打印一条错误信息,而不让异常继续传播。在与用户进行交互式的会话中,使用这个功能很有用;但是在程序内部使用,引发异常是最好的选择。(此时应该关闭抑制功能)

>>> class MuffledCalculator:
    muffed = False  关闭抑制功能
    def calc(self,expr):
        try:
            return eval(expr)
        except ZeroDivisionError:
            if self.muffled:
                print('Division by zero  is illegal')
            else:
                raise        
>>>

注意:发生除零的行为时,如果启用了抑制功能,方法calc将隐式的返回 none。换而言之,如果启用了印制功能,就不应依赖返回值。

>>> calculation = MuffledCalculator()
>>> calculation.calc('10/2')
5.0
关闭抑制功能
>>> calculation.calc('10/0')
Traceback (most recent call last):
  File "<pyshell#10>", line 5, in calc
    return eval(expr)
  File "<string>", line 1, in <module>
ZeroDivisionError: division by zero

开启抑制功能
>>> calculation.muffled = True
>>> calculation.calc('10/0')
Division by zero  is illegal
>>>

关闭了抑制功能,捕获了异常ZeroDivisionError,但是继续向上传播它。

如果无法处理异常,在except子句中使用不带参数的 raise 通常是不错的,但有时你可能想引发别的异常。在这种情况下,导致进入except子句的异常被作为异常上下文存储起来,并出现在最终的错误消息中,可以使用 raise … from … 语句来提供自己的异常上下文,也可以使用None来禁用上下文。

>>> try:
    1/0
except ZeroDivisionError:
    raise ValueError

Traceback (most recent call last):
  File "<pyshell#94>", line 2, in <module>
    1/0
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<pyshell#94>", line 4, in <module>
    raise ValueError
ValueError
>>> try:
    1/0
except ZeroDivisionError:
    raise ValueError from None

Traceback (most recent call last):
  File "<pyshell#70>", line 4, in <module>
    raise ValueError from None
ValueError
>>>

(2) 多个except 子句:

>>> try:
    x = int(input('Enter  the first number: '))
    y = int(input('Enter  the second number: '))
    print(x / y)
except  ZeroDivisionError:
    print("the second number  can't be zero")
except  ValueError:
    print("That wasn't  a number, was it ")


Enter  the first number: 10
Enter  the second number: "hello"
That wasn't  a number, was it

(3) 一箭双雕:

如果使用一个except 子句捕获异常,可以在一个元组中指定这些异常,如下所示:

>>> try:
    x = int(input('Enter  the first number: '))
    y = int(input('Enter  the second number: '))
    print(x / y)
except  (ZeroDivisionError,TypeError,ValueError,NameError):
    print("your numbers were bogus ..")


Enter  the first number: 10
Enter  the second number: "hell"
your numbers were bogus ..

(4)捕获对象:

要在except子句中访问异常对象本身,可以使用两个而不是一个参数。(即便你在捕获多个异常时,也只向except提供了一个参数——元组)需要让程序继续运行并记录错误,指向用户显示。

>>> try:
    x = int(input('Enter  the first number: '))
    y = int(input('Enter  the second number: '))
    print(x / y)
except  (ZeroDivisionError,TypeError,ValueError,NameError) as a:
    print(a)


Enter  the first number: 10
Enter  the second number: "hello"
invalid literal for int() with base 10: '"hello"'

(5)一网打尽:

即使你处理了好几种异常,但可能还有一些漏网之鱼,例如:用户在不提示任何输入内容就按回车键,将出现一条错误信息,还有一些相关问题出在什么地方(栈跟踪)。

如果要使用一段代码捕获所有的异常,只需在except语句中不指定给任何类即可:

try:
    x = int(input('Enter  the first number: '))
    y = int(input('Enter  the second number: '))
    print(x / y)
except :
    print("something  wrong happend")

(6) 异常循环:

>>> while True:
    try:
        x = int(input('Enter  the first number: '))
        y = int(input('Enter  the second number: '))
        value = x / y
        print('x / y is',value)
    except Exception as e:
        print('Invalid inputL',e)
        print('Please try again')
    else:
        break

(7)try..finally语句

try…finally…语句用来表达这样的情况:

在程序中,如果一个段代码必须要执行,即无论异常是否产生都要执行,那么此时就需要使用finally。 比如文件关闭,释放锁,把数据库连接返还给连接池等。

try:
    f = open('test.txt')
    try:
        while True:
            content = f.readline()
            if len(content) == 0:
                break
            print(content)
    except:
        #如果在读取文件的过程中,产生了异常,那么就会捕获到
        #比如 按下了 ctrl+c
        pass
    finally:
        f.close()
        print('关闭文件')
except:
    print("没有这个文件")

说明:
我们可以观察到KeyboardInterrupt异常被触发,程序退出。但是在程序退出之前,finally从句仍然被执行,把文件关闭。

0x04:异常和函数

异常和函数有着天然的联系。如果不处理函数中引发的异常,它将向上传播到调用函数的地方中。如果在那里也没有得到处理,异常将继续传播,直至达到主程序(全局作用域)。

如果主程序没有异常处理程序,程序将终止并显示站跟踪消息。

def  faulty():
    raise Exception('Something is  wrong')

def  ingore_exception():
    faulty()

def  handle_exception():
    try:
        faulty()
    except:
warnings.filterwarnings(action,category=warning,...)   用于过滤警告
warnings.warn(message,category=None)  用于发出警告

如果你只想发出警告,指出情况偏离了正轨,可以使用模块 warnings中的函数 warn。
警告只显示一次,如果在运行最后一行代码,什么事情也不会发生。

>>> from warnings import warn
>>> warn("I've got a bad feeling  about this. ")

Warning (from warnings module):
  File "<pyshell#198>", line 1
UserWarning: I've got a bad feeling  about this. 
>>> warn("I've got a bad feeling  about this. ")
>>>

如果其他代码在使用你的模块,可以使用模块 warnings中的函数 filterwarnings 来抑制你发出的警告(或特定类型的警告),并指出采取的措施,如:”error“或”ignore“。

>>> from warnings  import filterwarnings
>>> filterwarnings('ignore')
>>> warn("Anyone out there?")
>>> filterwarnings("error")
>>> warn("something is very wrong")
Traceback (most recent call last):
  File "<pyshell#210>", line 1, in <module>
    warn("something is very wrong")
UserWarning: something is very wrong
>>>