H5W3
当前位置:H5W3 > 其他技术问题 > 正文

Python中的异常和错误处理

本文概述

介绍

Python中的异常和错误处理2

资源

在我们解释为什么异常处理必不可少以及Python支持的内置异常类型之前, 有必要了解错误和异常之间存在细微的差别。

错误无法处理, 而Python异常可以在运行时处理。错误可以是语法(解析)错误, 而在执行过程中可能会发生多种类型的异常, 并且这些异常并非无条件地不可操作。错误可能表明一个合理的应用程序不应尝试捕获的严重问题, 而异常可能表明一个应用程序应尝试捕获的条件。错误是未经检查的异常的一种形式, 并且是不可恢复的, 就像OutOfMemoryError一样, 程序员不应该尝试处理。

异常处理使你的代码更健壮, 并有助于防止可能导致程序以不受控制的方式停止的潜在故障。想象一下, 如果你编写了仍在生产环境中部署的代码, 但该代码由于异常而终止, 那么你的客户将不会满意, 因此最好事先处理特定的异常并避免混乱。

错误可能有多种类型:

  • 语法错误
  • 内存不足错误
  • 递归错误
  • 例外情况

让我们一一看。

语法错误

语法错误通常称为解析错误, 主要是当解析器在代码中检测到语法问题时引起的。

让我们以一个例子来理解它。

a = 8
b = 10
c = a b
  File "<ipython-input-8-3b3ffcedf995>", line 3
    c = a b
          ^
SyntaxError: invalid syntax

上面的箭头指示执行代码时解析器何时发生错误。箭头前面的令牌会导致失败。为了纠正此类基本错误, Python将完成你的大部分工作, 因为它将为你打印出发生错误的文件名和行号。

内存不足错误

内存错误主要取决于你的系统RAM, 并且与堆有关。如果内存中有大对象(或被引用的对象), 则将看到OutofMemoryError(源)。可能由于多种原因引起:

  • 使用32位Python架构(给出的最大内存分配非常低, 介于2GB-4GB之间)。
  • 加载非常大的数据文件
  • 运行机器学习/深度学习模型等等。

你可以借助异常处理来处理内存错误, 这是解释器完全耗尽内存并且必须立即停止当前执行时的后备异常。在这些罕见的情况下, Python会引发OutofMemoryError, 从而允许脚本以某种方式捕获自身并摆脱内存错误并自行恢复。

但是, 由于Python采用了C语言的内存管理体系结构(malloc()函数), 因此不确定脚本的所有进程都将恢复—在某些情况下, MemoryError将导致无法恢复的崩溃。因此, 将异常处理用于此类错误不是一种好的做法, 也不可取。

递归错误

它与堆栈有关, 在调用函数时发生。顾名思义, 当执行太多方法时, 递归错误就会发生, 其中一种方法在内部执行(一种无限递归), 这受堆栈大小的限制。

你所有与变量相关联的本地变量和方法将被放置在堆栈中。对于每个方法调用, 将创建一个堆栈框架, 并将本地以及与方法调用相关的数据放置在该堆栈框架内。一旦方法执行完成, 堆栈框架将被删除。

要重现此错误, 让我们定义一个将要递归的函数递归, 这意味着它将继续将自己作为无限循环方法调用来调用, 你将看到StackOverflow或一个递归错误, 因为每次调用时都会用方法数据填充堆栈框架, 但它不会被释放。

def recursion():
    return recursion()
recursion()
---------------------------------------------------------------------------

RecursionError                            Traceback (most recent call last)

<ipython-input-3-c6e0f7eb0cde> in <module>
----> 1 recursion()


<ipython-input-2-5395140f7f05> in recursion()
      1 def recursion():
----> 2     return recursion()


... last 1 frames repeated, from the frame below ...


<ipython-input-2-5395140f7f05> in recursion()
      1 def recursion():
----> 2     return recursion()


RecursionError: maximum recursion depth exceeded

压痕误差

缩进错误在本质上与语法错误相似, 并且属于该错误。但是, 特定于脚本中唯一与缩进相关的问题。

因此, 让我们以一个简单的示例来了解缩进错误。

for i in range(10):
print('Hello world')
  File "<ipython-input-6-628f419d2da8>", line 2
    print('Hello world')
        ^
IndentationError: expected an indented block

例外情况

即使语句或表达式的语法正确, 执行时仍可能导致错误。 Python异常是在执行期间检测到的错误, 它们并非无条件致命:你将在本教程中很快学习如何在Python程序中处理它们。当Python脚本引发异常时, 将创建一个异常对象。如果脚本明确不处理该异常, 则该程序将被迫突然终止。

程序通常不处理异常, 并导致错误消息, 如下所示:

类型错误

a = 2
b = 'srcmini'
a + b
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-7-86a706a0ffdf> in <module>
      1 a = 2
      2 b = 'srcmini'
----> 3 a + b


TypeError: unsupported operand type(s) for +: 'int' and 'str'

零分误差

100 / 0
---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

<ipython-input-43-e9e866a10e2a> in <module>
----> 1 100 / 0


ZeroDivisionError: division by zero

Python异常的类型多种多样, 并且该类型作为消息的一部分进行打印:上面两个示例中的类型是ZeroDivisionError和TypeError。打印为异常类型的两个错误字符串都是Python内置异常的名称。

错误行的其余部分根据异常类型提供了导致错误的原因的详细信息。

现在让我们看一下Python的内置异常。

内置异常

Python中的异常和错误处理3

资源

在开始学习内置异常之前, 让我们快速修改异常处理的四个主要组件, 如下图所示。

  • 尝试:它将运行你期望在其中发生错误的代码块。
  • 除了:在这里, 你将在try块中定义期望的异常类型(内置或自定义)。
  • 否则:如果没有任何异常, 则将执行此代码块(如果希望脚本的一部分产生异常, 则将此作为补救措施或后备选项)。
  • 最后:无论是否有异常, 该代码块将始终执行。

在本教程的以下部分中, 你将了解异常的常见类型, 并学习在异常处理的帮助下进行处理。

键盘中断错误

当你尝试通过在命令行中按ctrl + c或ctrl + z来停止正在运行的程序或在Jupyter Notebook中中断内核时, 将引发KeyboardInterrupt异常。有时你可能不打算中断程序, 但是由于错误而发生, 在这种情况下, 使用异常处理来避免此类问题可能会有所帮助。

在下面的示例中, 如果运行单元并中断内核, 则程序将引发KeyboardInterrupt异常。 inp = input()现在让我们处理KeyboardInterrupt异常。

try:
    inp = input()
    print ('Press Ctrl+C or Interrupt the Kernel:')
except KeyboardInterrupt:
    print ('Caught KeyboardInterrupt')
else:
    print ('No exception occurred')
Caught KeyboardInterrupt

标准误差

让我们了解一些在编程时通常会发生的标准错误。

算术误差

  • 零分误差
  • 溢出错误
  • 浮点误差

上面讨论的所有上述异常都属于Arithmetic基类, 并且针对算术运算中的错误而引发。

零师

当除数(除法的第二个参数)或分母为零时, 结果将产生零除法误差。

try:  
    a = 100 / 0
    print (a)
except ZeroDivisionError:  
        print ("Zero Division Exception Raised." )
else:  
    print ("Success, no error!")
Zero Division Exception Raised.

溢出错误

算术运算的结果超出范围时, 将引发溢出错误。对于超出所需范围的整数, 将引发OverflowError。

try:  
    import math
    print(math.exp(1000))
except OverflowError:  
        print ("OverFlow Exception Raised.")
else:  
    print ("Success, no error!")
OverFlow Exception Raised.

断言错误

当断言语句失败时, 将引发断言错误。

让我们举一个例子来理解断言错误。假设你有两个变量a和b, 需要对其进行比较。要检查a和b是否相等, 请在此之前应用assert关键字, 当表达式返回false时, 它将引发Assertion异常。

try:  
    a = 100
    b = "srcmini"
    assert a == b
except AssertionError:  
        print ("Assertion Exception Raised.")
else:  
    print ("Success, no error!")
Assertion Exception Raised.

属性错误

当引用不存在的属性, 并且该属性引用或分配失败时, 将引发属性错误。

在下面的示例中, 你可以观察到Attributes类对象没有具有name属性的属性。

class Attributes(object):
    a = 2
    print (a)

try:
    object = Attributes()
    print (object.attribute)
except AttributeError:
    print ("Attribute Exception Raised.")
2
Attribute Exception Raised.

导入错误

当你尝试导入在其标准路径中不存在(无法加载)的模块, 或者甚至在模块名称中输入错误时, 都会引发ImportError。

import nibabel
---------------------------------------------------------------------------

ModuleNotFoundError                       Traceback (most recent call last)

<ipython-input-6-9e567e3ae964> in <module>
----> 1 import nibabel


ModuleNotFoundError: No module named 'nibabel'

查找错误

当在列表/字典的映射或序列上使用的键或索引无效或不存在时, Lookup Error用作发生异常的基类。

引发的两种异常类型是:

  • IndexError
  • KeyError

关键错误

如果在字典中找不到你要访问的密钥, 则会引发密钥错误异常。

try:  
    a = {1:'a', 2:'b', 3:'c'}  
    print (a[4])  
except LookupError:  
    print ("Key Error Exception Raised.")
else:  
    print ("Success, no error!")
Key Error Exception Raised.

索引错误

当你尝试访问该列表中不存在或超出该列表范围的列表的索引(序列)时, 将引发索引错误。

try:  
    a = ['a', 'b', 'c']  
    print (a[4])  
except LookupError:  
    print ("Index Error Exception Raised, list index out of range")
else:  
    print ("Success, no error!")
Index Error Exception Raised, list index out of range

记忆体错误

如前所述, 当操作没有足够的内存来进一步处理时, 会引发”内存错误”。

名称错误

如果找不到本地或全局名称, 则会引发名称错误。

在以下示例中, 未定义ans变量。因此, 你将收到名称错误。

try:
    print (ans)
except NameError:  
    print ("NameError: name 'ans' is not defined")
else:  
    print ("Success, no error!")
NameError: name 'ans' is not defined

运行时错误

未实施错误

教程的此部分从此源中派生。运行时错误充当NotImplemented错误的基类。当派生类覆盖该方法时, 用户定义类中的抽象方法应引发此异常。

class BaseClass(object):
    """Defines the interface"""
    def __init__(self):
        super(BaseClass, self).__init__()
    def do_something(self):
        """The interface, not implemented"""
        raise NotImplementedError(self.__class__.__name__ + '.do_something')

class SubClass(BaseClass):
    """Implementes the interface"""
    def do_something(self):
        """really does something"""
        print (self.__class__.__name__ + ' doing something!')

SubClass().do_something()
BaseClass().do_something()
SubClass doing something!



---------------------------------------------------------------------------

NotImplementedError                       Traceback (most recent call last)

<ipython-input-1-57792b6bc7e4> in <module>
     14
     15 SubClass().do_something()
---> 16 BaseClass().do_something()


<ipython-input-1-57792b6bc7e4> in do_something(self)
      5     def do_something(self):
      6         """The interface, not implemented"""
----> 7         raise NotImplementedError(self.__class__.__name__ + '.do_something')
      8
      9 class SubClass(BaseClass):


NotImplementedError: BaseClass.do_something

类型错误

当两种不同或不相关类型的操作数或对象组合在一起时, 将引发类型错误异常。

在下面的示例中, 将添加整数和字符串, 这将导致类型错误。

try:
    a = 5
    b = "srcmini"
    c = a + b
except TypeError:
    print ('TypeError Exception Raised')
else:
    print ('Success, no error!')
TypeError Exception Raised

值错误

当内置操作或函数接收到类型正确但值无效的参数时, 将引发值错误。

在下面的示例中, 内置操作float接收一个参数, 该参数是一个字符序列(值), 对于类型float无效。

try:
    print (float('srcmini'))
except ValueError:
    print ('ValueError: could not convert string to float: \'srcmini\'')
else:
    print ('Success, no error!')
ValueError: could not convert string to float: 'srcmini'

Python自定义异常

教程的此部分从此源中派生。

如本教程上一节所述, Python具有许多内置异常, 你可以在程序中使用它们。尽管如此, 有时你可能仍需要使用自定义消息创建自定义例外, 以达到你的目的。

你可以通过创建一个新类来实现此目的, 该类将从Python中预定义的Exception类派生。

class UnAcceptedValueError(Exception):   
    def __init__(self, data):    
        self.data = data
    def __str__(self):
        return repr(self.data)

Total_Marks = int(input("Enter Total Marks Scored: "))
try:
    Num_of_Sections = int(input("Enter Num of Sections: "))
    if(Num_of_Sections < 1):
        raise UnAcceptedValueError("Number of Sections can't be less than 1")
except UnAcceptedValueError as e:
    print ("Received error:", e.data)
Enter Total Marks Scored: 10
Enter Num of Sections: 0
Received error: Number of Sections can't be less than 1

在上面的示例中, 你观察到如果输入的值小于1, 则将引发并处理自定义异常。许多标准模块定义其异常, 以报告其定义的功能中可能发生的错误。

Python异常处理的缺点

利用Python异常处理也有副作用。像这样, 使用try-except块来处理异常的程序运行会稍慢一些, 并且代码的大小也会增加。

下面是一个示例, 其中Python的timeit模块用于检查2条不同语句的执行时间。在stmt1中, try-except用于处理ZeroDivisionError, 而在stmt2中, 如果if语句用作常规检查条件。然后, 使用变量a = 0将这些语句执行10000次。这里要注意的是, 这两个语句的执行时间不同。你会发现正在处理异常的stmt1比stmt2花费了更长的时间, 后者仅检查值, 如果不满足条件则不执行任何操作。

因此, 你应该限制Python异常处理的使用, 并且仅在极少数情况下使用它。例如, 当你不确定输入是用于算术运算的整数还是浮点数, 或者不确定在尝试打开文件时是否存在文件。

import timeit
setup="a=0"
stmt1 = '''\
try:
    b=10/a
except ZeroDivisionError:
    pass'''
stmt2 = '''\
if a!=0:
    b=10/a'''
print("time=", timeit.timeit(stmt1, setup, number=10000))
print("time=", timeit.timeit(stmt2, setup, number=10000))
time= 0.003897680000136461
time= 0.0002797570000439009

祝贺你完成本教程。

如你所知, 异常处理通过提供一种解耦Python错误处理并使代码更健壮的机制, 有助于打破程序的典型控制流。

除了增加单元测试和面向对象的编程之外, Python的出色处理是使你的代码可用于生产和未来的主要因素之一。

这是一项强大的技术, 仅是四个块的概念。 try块查找由代码引发的异常, 而except块处理这些异常(内置和自定义)。

对所有人来说, 一项不错的练习是使用异常处理的所有四个组件, 并尝试使代码更健壮。

请随时在下面的评论部分中提出与本教程相关的任何问题。

本文地址:H5W3 » Python中的异常和错误处理

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址