【Python 基础教程】 第8章 异常


8.1 异常是什么

>>> 1/0

---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-85-05c9758a9c21> in <module>()
----> 1 1/0

ZeroDivisionError: division by zero

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

每个异常都是每个类(这里是 ZeroDivisionError)的实例。你能以各种方式引发和捕获这些实例,从而逮住错误并采取措施而不是放任整个程序的失败。


8.2 让事情沿你指定的轨道出错

如何自主地引发异常,如何创建异常,如何处理异常


8.2.1 raise 语句

引发异常。

# 增加一个异常并添加了错误信息的注释
>>> raise Exception("这里有一个错误") 

---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-89-5f15955ab2d6> in <module>()
----> 1 raise Exception("这里有一个错误")

Exception: 这里有一个错误

Exception 是Python的内置异常类,表示通用异常,没有指出什么错误。其他的内置异常类,都是从 Exception 派生出来的。

Python内置异常类层次关系图:

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception          #本章介绍的异常类
      +-- StopIteration
      +-- StandardError
      |    +-- BufferError
      |    +-- ArithmeticError
      |    |    +-- FloatingPointError
      |    |    +-- OverflowError
      |    |    +-- ZeroDivisionError
      |    +-- AssertionError
      |    +-- AttributeError 
      |    +-- EnvironmentError
      |    |    +-- IOError
      |    |    +-- OSError
      |    |         +-- WindowsError (Windows)
      |    |         +-- VMSError (VMS)
      |    +-- EOFError
      |    +-- ImportError
      |    +-- LookupError
      |    |    +-- IndexError
      |    |    +-- KeyError
      |    +-- MemoryError
      |    +-- NameError
      |    |    +-- UnboundLocalError
      |    +-- ReferenceError
      |    +-- RuntimeError
      |    |    +-- NotImplementedError
      |    +-- SyntaxError
      |    |    +-- IndentationError
      |    |         +-- TabError
      |    +-- SystemError
      |    +-- TypeError
      |    +-- ValueError
      |         +-- UnicodeError
      |              +-- UnicodeDecodeError
      |              +-- UnicodeEncodeError
      |              +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
       +-- ImportWarning
       +-- UnicodeWarning
       +-- BytesWarning

8.2.2 自定义的异常类

# 自定义异常类,可以添加方法
class SomeException(Exception):
    pass

Ps:如何使用自定义的异常类?


8.3 捕获异常

# 输入两个数作除法

x = int(input("输入除数:"))
y = int(input("输入被除数:"))
print("x/y = ",x/y)


----------
输入除数:1
输入被除数:0
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-92-aa1da2ce6cb0> in <module>()
      1 x = int(input("输入除数:"))
      2 y = int(input("输入被除数:"))
----> 3 print("x/y = ",x/y)

ZeroDivisionError: division by zero
# 捕获异常,并对错误进行处理(此处打印出错误消息)
try:
    x = int(input("输入除数:"))
    y = int(input("输入被除数:"))
    print("x/y = ",x/y)
except ZeroDivisionError:
    print("错误:被除数不能为 0 !")


----------
输入除数:1
输入被除数:0
错误:被除数不能为 0

8.3.1 不用提供参数

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

下面提供了一个能够“抑制“异常的计算器类。如果启用功能,将打印一条错误信息,而不让异常继续传播;如果未启用,则异常继续传播。

# 抑制异常计算器
class MuffledCalculator:
    muffled = False 
    def calc(self,expr):
        try:
            return eval(expr)
        except ZeroDIvisionError:
            if self.muffled: # 如果 muffled 为 True,则异常被抑制,输出错误信息
                print('被除数不能为 0 !')
            else:            # 如果 muffled 为 False,则异常能够继续传播
                raise

如果捕获了异常还想要引发其他异常,导致进入 except 字句的异常将被作为异常上下文存储起来,并出现在最终的错误消息中。如下所示:

try:
    1/0
except ZeroDivisionError("被除数不能为 0 "):
    raise ValueError("这是引发的第二个异常")


----------
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-100-a442a12bba7f> in <module>()
      1 try:
----> 2         1/0
      3 except ZeroDivisionError("被除数不能为 0 "):

ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

---------------------------------------------------------------------------
# 处理完上面的异常后,引发了这个异常

TypeError                                 Traceback (most recent call last)
<ipython-input-100-a442a12bba7f> in <module>()
      1 try:
      2         1/0
----> 3 except ZeroDivisionError("被除数不能为 0 "):
      4         raise ValueError("这是引发的第二个异常")

TypeError: catching classes that do not inherit from BaseException is not allowed

----------


8.3.2 多个 except 子句


# 捕获异常,并对错误进行处理(此处打印出错误消息)
try:
    x = int(input("输入除数:"))
    y = int(input("输入被除数:"))
    print("x/y = ",x/y)
except ZeroDivisionError:
    print("错误:被除数不能为 0 !")


----------
输入除数:5
输入被除数:啊
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-112-693d5bd77e92> in <module>()
      2 try:
      3         x = int(input("输入除数:"))
----> 4         y = int(input("输入被除数:"))
      5         print("x/y = ",x/y)
      6 except ZeroDivisionError:

ValueError: invalid literal for int() with base 10: '啊'

通过上面的代码我们发现,在输入一个非数字的‘啊‘后,引发了另外一种异常。且该程序中的 except 只能捕获 ZeroDivisionError 异常,这种异常将成为漏网之鱼,导致程序终止。为同时捕获这种异常,可再添加一个 exceept 子句。

# 捕获异常,并对错误进行处理(此处打印出错误消息)
try:
    x = int(input("输入除数:"))
    y = int(input("输入被除数:"))
    print("x/y = ",x/y)
except ZeroDivisionError:
    print("错误:被除数不能为 0 !")
except TypeError:
    print("嘿老兄,我可不认得除了 0123456789 之外的东西!")

⚠️ 异常处理并不会导致代码混乱,而挺假大量的 if 语句来检查各种可能的错误状态将导致代码的可读性极差。


8.3.3 一箭双雕

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

try:
    x = int(input("输入除数:"))
    y = int(input("输入被除数:"))
    print("x/y=",x/y)
except(ZeroDivisionError, TypeError, NameError):
    print("your numbers were bogus ...  ")

8.3.4 捕获对象

要在 except 子句中访问异常对象本身,可使用两个而不是一个参数。(⚠️即便是在你捕获多个异常时,也只是想 except 提供了一个参数——一个元组。)需要让程序继续运行并记录错误(可能只是向用户显示)时,这很有用。下面的示例程序打印发生的异常并继续运行:

# 捕获异常并访问异常对象本身
try:
    x = int(input("输入除数:"))
    y = int(input("输入被除数:"))
    print("x/y=",x/y)
except(ZeroDivisionError, TypeError, NameError) as e:
    print(e)


----------
# 访问异常对象本身 division by zero
输入除数:1
输入被除数:0
division by zero

8.3.5 except Exception as e 捕获漏网之鱼

即使程序处理了好几种异常,还是可能有一些漏网之鱼。那么,此时不如让程序马上崩溃,这样就知道了什么地方除了问题。

然而,如果就是要用一段代码捕获所有的异常的化,只需在 except 子句中不制定任何异常类即可。

# 捕获任意异常
try:
    x = int(input("输入除数:"))
    y = int(input("输入被除数:"))
    print("x/y=",x/y)
except:
    print("Somthing wrong happened ..." )

但是这样根本不是到错在哪儿了,因此,更好的选择是使用

except Exception as e

并对异常现象进行检查。这样将让不是从 Exception 派生而来的为数不多的异常成为漏网之鱼。【详情见 8.2.1 的“Python内置异常类层次关系图“】


8.3.6 循环处理异常

# 发生异常时,根据提示的异常信息调整输入,直至输入正确没有异常后跳出循环
while True: 
    try:
        x = int(input("输入除数:"))
        y = int(input("输入被除数:"))
        print("x/y=",x/y)
    except Exception as e: # 当出现异常时,返回异常对象的异常信息
        print("错误原因:",e )
        print("请重新输入:")
    else: # 只有当没有异常时,才跳出 while 循环
        break 

8.3.7 Finally

finally 子句,可用于在发生异常时执行‘毁灭世界‘工作。这个子句是与 try 子句配套的。

# 当发生异常,执行 finally 子句
try:
    x = 1/0
finally:
    print('正在毁灭世界...')
    del x

NameError 为什么返回的是 ZeroDivisionError?

# 可以包含try、except、else(或其中的3个)
try:
    1/0
except NameError:
    print('Unknow variable')
else:
    print("That went well!")
finally:
    print('我正在毁灭世界...')


----------
我正在毁灭世界...
---------------------------------------------------------------------------
# NameError 为什么返回的是 ZeroDivisionError?
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-11-ee56fa0deca8> in <module>()
      1 try:
----> 2         1/0
      3 except NameError:
      4         print('Unknow variable')
      5 else:

ZeroDivisionError: division by zero

8.4 异常和函数

如果不处理函数中引发的异常,它将一直向上传播到调用函数的地方,如果仍未得到处理,异常将继续传播,直至达到主程序(全局作用域)。如果主程序中也没有异常处理程序,程序将中止并显示栈跟踪消息。


8.5 异常之禅

给出如下代码,进行简介性优化

def describe_persion(persion):
    print("名字:",persion['name'])
    print("年龄:",persion['age'])
    if "occupation" in persion:
        print("职业:",persion['occupation'])

# 代码待优化问题:在条件语句中,需要对persion'进行两次查找。
# 在这里,如果字典中没有'occupation'键,将引发异常 KeyError,对异常进行 pass 处理,忽略了异常。 
def describe_persion(persion):
    '引用了异常机制来优化条件语句'
    print("名字:",persion['name'])
    print("年龄:",persion['age'])
    try:
        print("职业:",persion['occupation'])
    except KeyError: # KeyEror:使用映射中不存在的键时引发
        pass         # 忽略这个异常


----------
>>> persion = {'name':'Wei Wu','age':18}
>>> describe_persion(persion)
名字: Wei Wu
年龄: 18
'定义一个类'
class Class:
    def set_name(self,name):
        self.name = name
>>> c = Class()
>>> c.set_name = 'Wei Wu'

'检查对象是否包含特定的属性'
try:
    c.name
except AttributeError: # 引用属性给它赋值失败时触发
    print("The object is not writeable")
else:
    print("The object is writeable")

这里采用 try/except 语句的方式可替代在 7.2.9节中的方式,但这两种方式在效率提升方面相差微乎其微。

>>> hasattr(c,'name')
True

在很多情况下,相比于使用 if/else,使用 try/except 更加自然、更加符合 Python 风格。


8.6 不那么异常的情况

如果只是想发出警告,指出情况偏离了正轨,可使用模块 warnings 中的函数 warn。

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


----------
'警告只显示一次。'
/Applications/anaconda3/lib/python3.6/site-packages/ipykernel_launcher.py:2: UserWarning: I've got a bad feeling about this

通过 filterwarnings 函数来抑制发出的特定警告

>>> from warnings import filterwarnings
>>> filterwarnings("ignore")    # ignore(忽视),会忽视掉警告。
>>> warn("Anyone out there?")   # 这条警告不会显示

>>> filterwarnings("error")     # 异常会显示
>>> warn("Something is wrong!")

----------
---------------------------------------------------------------------------
UserWarning                               Traceback (most recent call last)
<ipython-input-63-df3903e537a5> in <module>()
      1 filterwarnings('error')
----> 2 warn("Something is wrong!")

UserWarning: Something is wrong!

发出警告时,可以指定将要引发的异常(必须是 Warning的子类),也可根据异常来过滤掉特定类型的警告

      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
# 引发了 DeprecationWarning 的警告
>>> filterwarnings('error')
>>> warn('The function is really old ...',DeprecationWarning)


---------------------------------------------------------------------------
DeprecationWarning                        Traceback (most recent call last)
<ipython-input-64-a4397c211662> in <module>()
      1 filterwarnings('error')
----> 2 warn('The function is really old ...',DeprecationWarning)

DeprecationWarning: The function is really old ...



# 抑制了 DeprecationWarning 的警告
>>> filterwarnings('ignore',category = DeprecationWarning)
>>> warn('Another deprecation warning',DeprecationWarning)

# 发出警告
>>> warn("Sonething wrong.")


---------------------------------------------------------------------------
UserWarning                               Traceback (most recent call last)
<ipython-input-66-ea06672df1e5> in <module>()
----> 1 warn('something wrong')

UserWarning: something wrong

8.7 小结

  • 异常对象:异常情况是用异常对象表示的。对于异常情况,有多种处理方式;如果忽略,将导致程序终止。

  • 引发异常:可使用 raise 语句来引发异常,它将一个异常类或异常实例作为参数,但你也可提供两个参数(异常和错误消息)[8.2.1]。如果在 excepy 子句中调用 raise 时没有提供任何参数,它将重新引发该子句捕获的异常 [8.3.1]。

  • 自定义的异常类:你可以通过从 Exception 派生来创建自定义的异常 [8.2.2]。

  • 捕获异常:要捕获异常,可在 try 语句中使用 except 子句 [8.3]。在 except 子句中,如果没有指定异常类,将捕获所有异常 [8.3.5]。你可指定多个异常类,方法是将它们放在元组中 [8.3.3] 。如果向 except 提供两个参数,第二个参数将关联到异常对象 [8.3.4]。在同一条 try/except 语句中,可包含多个 except 子句,以便对不同的异常采取不同的措施 [8.3.2]。

  • else 子句: 除了 except 子句外,你还可以使用 else 子句,它在主 try 块没有引发异常时执行 [8.3.6]。

  • finally:要确保代码块(如执行毁灭世界程序)无论是否引发异常都将执行,可使用 try/finally 并将代码块(毁灭世界)放在 finally 子句中 [8.3.7]。

  • 异常和函数:在函数中引发异常时,异常将传播到调用函数的地方。[8.4]

  • 警告:警告类似于异常,但(通常)只打印一条错误消息。你可指定警告类别,它们是 Warning 的子类。[8.6]

个人总结

(1)异常对象 - 8。1
Python 使用异常对象来表示异常状态,并在程序遇到错误时引发异常;如果异常未被处理,程序将被中断并返回异常信息。异常对象如下所示,其中 Exception 和 Warning 是在本章中所学习到的。

Python内置异常类层次关系图:

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception          #本章介绍的异常类
      +-- StopIteration
      +-- StandardError
      |    +-- BufferError
      |    +-- ArithmeticError
      |    |    +-- FloatingPointError
      |    |    +-- OverflowError
      |    |    +-- ZeroDivisionError
      |    +-- AssertionError
      |    +-- AttributeError 
      |    +-- EnvironmentError
      |    |    +-- IOError
      |    |    +-- OSError
      |    |         +-- WindowsError (Windows)
      |    |         +-- VMSError (VMS)
      |    +-- EOFError
      |    +-- ImportError
      |    +-- LookupError
      |    |    +-- IndexError
      |    |    +-- KeyError
      |    +-- MemoryError
      |    +-- NameError
      |    |    +-- UnboundLocalError
      |    +-- ReferenceError
      |    +-- RuntimeError
      |    |    +-- NotImplementedError
      |    +-- SyntaxError
      |    |    +-- IndentationError
      |    |         +-- TabError
      |    +-- SystemError
      |    +-- TypeError
      |    +-- ValueError
      |         +-- UnicodeError
      |              +-- UnicodeDecodeError
      |              +-- UnicodeEncodeError
      |              +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
       +-- ImportWarning
       +-- UnicodeWarning
       +-- BytesWarning

(2)引发异常(raise) - 8。2
若要引发异常,使用 raise 语句

#  raise 后指定一个 Exception 子类,并可添加异常信息
raise Exception_class[('指定的异常信息')]

(3)自定义异常类 - 8。3
可用 Exception 派生自定义异常类

class SomeCustomException(Exception):pass

(4)捕获异常

  • 使用 try/except 捕获异常。
  • 当 except 子句未指定异常类时,将捕获所有异常。
  • 若指定了异常类,将捕获指定异常,同时可以指定多个异常,在一个元组中指定多个异常类
  • 可以由多个 except 子句来分别捕获不同的异常类
  • 为 try/except 语句添加 else 子句,如果没有异常则执行另外一个模块
  • except class_Error as e ,将返回对象本身,可以让程序记住或向用户显示错误信息
  • finally,不论是否发生异常都执行 finally 子句,在执行完 finally 子句后引发异常(举例:某公交司机在心脏病发作死亡前拉下公交车制动挽救一车人性命)

(5)异常和函数
当函数内部的异常未得到处理,异常将会传播至调用函数的地方,程序将中止并显示栈跟踪消息。

(6)警告
如果想发出警告,调用 python 模块 warnings 中的函数 warn,通常只打印一条错误消息

warnings.warn(message,category = None)
warn("这是一个警告")

如果选择性的警告,即抑制某些警告,使用模块 warnings 中的 filterwarnings 函数, filterwarnings 可以选择指定的警告(当然必须是 Warnings 的子类,见上面的 异常类层次图)

warnings.filterwarnings(action,category = Warning_class)
#[action = ('error','ignore')],error:引发, ignore:抑制

猜你喜欢

转载自blog.csdn.net/weixin_37392582/article/details/80354594
今日推荐