Python中的异常和错误处理

本文概述

  • 介绍
  • 例外情况
  • 内置异常
  • 标准误差
介绍
Python中的异常和错误处理

文章图片
资源
在我们解释为什么异常处理必不可少以及Python支持的内置异常类型之前, 有必要了解错误和异常之间存在细微的差别。
错误无法处理, 而Python异常可以在运行时处理。错误可以是语法(解析)错误, 而在执行过程中可能会发生多种类型的异常, 并且这些异常并非无条件地不可操作。错误可能表明一个合理的应用程序不应尝试捕获的严重问题, 而异常可能表明一个应用程序应尝试捕获的条件。错误是未经检查的异常的一种形式, 并且是不可恢复的, 就像OutOfMemoryError一样, 程序员不应该尝试处理。
异常处理使你的代码更健壮, 并有助于防止可能导致程序以不受控制的方式停止的潜在故障。想象一下, 如果你编写了仍在生产环境中部署的代码, 但该代码由于异常而终止, 那么你的客户将不会满意, 因此最好事先处理特定的异常并避免混乱。
错误可能有多种类型:
  • 语法错误
  • 内存不足错误
  • 递归错误
  • 例外情况
让我们一一看。
语法错误
语法错误通常称为解析错误, 主要是当解析器在代码中检测到语法问题时引起的。
让我们以一个例子来理解它。
a = 8 b = 10 c = a b

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

上面的箭头指示执行代码时解析器何时发生错误。箭头前面的令牌会导致失败。为了纠正此类基本错误, Python将完成你的大部分工作, 因为它将为你打印出发生错误的文件名和行号。
内存不足错误
【Python中的异常和错误处理】内存错误主要取决于你的系统RAM, 并且与堆有关。如果内存中有大对象(或被引用的对象), 则将看到OutofMemoryError(源)。可能由于多种原因引起:
  • 使用32位Python架构(给出的最大内存分配非常低, 介于2GB-4GB之间)。
  • 加载非常大的数据文件
  • 运行机器学习/深度学习模型等等。
你可以借助异常处理来处理内存错误, 这是解释器完全耗尽内存并且必须立即停止当前执行时的后备异常。在这些罕见的情况下, Python会引发OutofMemoryError, 从而允许脚本以某种方式捕获自身并摆脱内存错误并自行恢复。
但是, 由于Python采用了C语言的内存管理体系结构(malloc()函数), 因此不确定脚本的所有进程都将恢复—在某些情况下, MemoryError将导致无法恢复的崩溃。因此, 将异常处理用于此类错误不是一种好的做法, 也不可取。
递归错误
它与堆栈有关, 在调用函数时发生。顾名思义, 当执行太多方法时, 递归错误就会发生, 其中一种方法在内部执行(一种无限递归), 这受堆栈大小的限制。
你所有与变量相关联的本地变量和方法将被放置在堆栈中。对于每个方法调用, 将创建一个堆栈框架, 并将本地以及与方法调用相关的数据放置在该堆栈框架内。一旦方法执行完成, 堆栈框架将被删除。
要重现此错误, 让我们定义一个将要递归的函数递归, 这意味着它将继续将自己作为无限循环方法调用来调用, 你将看到StackOverflow或一个递归错误, 因为每次调用时都会用方法数据填充堆栈框架, 但它不会被释放。
def recursion(): return recursion()

recursion()

---------------------------------------------------------------------------RecursionErrorTraceback (most recent call last)< ipython-input-3-c6e0f7eb0cde> in < module> ----> 1 recursion()< ipython-input-2-5395140f7f05> in recursion() 1 def recursion(): ----> 2return recursion()... last 1 frames repeated, from the frame below ...< ipython-input-2-5395140f7f05> in recursion() 1 def recursion(): ----> 2return 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

---------------------------------------------------------------------------TypeErrorTraceback (most recent call last)< ipython-input-7-86a706a0ffdf> in < module> 1 a = 2 2 b = 'srcmini' ----> 3 a + bTypeError: unsupported operand type(s) for +: 'int' and 'str'

零分误差
100 / 0

---------------------------------------------------------------------------ZeroDivisionErrorTraceback (most recent call last)< ipython-input-43-e9e866a10e2a> in < module> ----> 1 100 / 0ZeroDivisionError: division by zero

Python异常的类型多种多样, 并且该类型作为消息的一部分进行打印:上面两个示例中的类型是ZeroDivisionError和TypeError。打印为异常类型的两个错误字符串都是Python内置异常的名称。
错误行的其余部分根据异常类型提供了导致错误的原因的详细信息。
现在让我们看一下Python的内置异常。
内置异常
Python中的异常和错误处理

文章图片
资源
在开始学习内置异常之前, 让我们快速修改异常处理的四个主要组件, 如下图所示。
  • 尝试:它将运行你期望在其中发生错误的代码块。

  • 除了:在这里, 你将在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

---------------------------------------------------------------------------ModuleNotFoundErrorTraceback (most recent call last)< ipython-input-6-9e567e3ae964> in < module> ----> 1 import nibabelModuleNotFoundError: 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!---------------------------------------------------------------------------NotImplementedErrorTraceback (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) 5def do_something(self): 6"""The interface, not implemented""" ----> 7raise 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 = http://www.srcmini.com/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块处理这些异常(内置和自定义)。
对所有人来说, 一项不错的练习是使用异常处理的所有四个组件, 并尝试使代码更健壮。
请随时在下面的评论部分中提出与本教程相关的任何问题。

    推荐阅读