本文概述
介绍
资源
在我们解释为什么异常处理必不可少以及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的内置异常。
内置异常
资源
在开始学习内置异常之前, 让我们快速修改异常处理的四个主要组件, 如下图所示。
- 尝试:它将运行你期望在其中发生错误的代码块。
- 除了:在这里, 你将在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块处理这些异常(内置和自定义)。
对所有人来说, 一项不错的练习是使用异常处理的所有四个组件, 并尝试使代码更健壮。
请随时在下面的评论部分中提出与本教程相关的任何问题。
评论前必须登录!
注册