最近,我的经理委派我创建一个自动报告。我设计的报告非常简单。它包括一些来自数据库的数字和一些基本的数学运算。我很兴奋最终可以向公司展示我的惊人的Python技能。
我完成并交付了产品。一切都很顺利。至少,直到大约两周后。我的报告由于除以零错误而随机失败了。来了个笑声轨道。
我的短篇故事缺少细节,但应该强调在编写程序时处理边界情况和错误的重要性。这份报告本应是展示我的Python技能的机会。然而,它却变成了一个有点尴尬的时刻。
所以,让我们花点时间学习使用Python标准库进行基本的错误处理。我将重点介绍一些你需要开始的东西。
在开始处理异常之前,你应该对Python基础知识有很好的掌握。你需要知道为什么会抛出异常才能处理它们!
(本文视频讲解:java567.com)
我们将介绍以下内容:
- Python中的Try和Except语句
- 使用Else子句进行条件执行
- 内置异常
- 自定义异常
- 性能考虑
Python中的Try和Except语句
try
和except
语句是处理异常的主要方法。它们的形式如下:
x = 0
try:print(5 / x)
except ZeroDivisionError:print("出错了")# 出错了
让我们来分析上面的代码,以确保我们理解一致:
- 第1行将值0赋给变量
x
- 第2行和第3行打开一个
try
子句,并尝试将5除以变量x
- 第4行和第5行打开一个
except
子句,用于任何ZeroDivisionError
,并指示程序在尝试将任何东西除以0时打印一条消息
你可能已经注意到了问题。我的变量x
的值是0,我试图将5除以x
。世界上最好的数学家也不能除以0,Python也不能。那么会发生什么呢?
如果我们不处理错误,程序在尝试将5除以x
时会立即终止。由于程序没有明确的指示如何处理异常,所以我们在第4行创建了except
子句,并提供了程序在尝试将某些东西除以0时要采取的步骤。
这就是处理异常的整个思想:当程序遇到无法简单忽略的错误时,你需要告诉程序该怎么做。让我们看看try
和except
子句是如何工作的。
解析Try语句
Try
和Except
语句遵循一种模式,允许你可靠地处理代码中的问题。我们来看看这个模式。
首先,try
子句中的代码尝试执行。
接下来,我们有三种可能性:
Try子句中没有错误
如果try
子句中的代码没有任何错误,程序将:
- 执行
try
子句 - 跳过所有
except
子句 - 继续正常运行
x = 1
try:print(5 / x)
except ZeroDivisionError:print("出错了")print("我在try子句之后执行!")# 5.0
# 我在try子句之后执行!
你可以看到,在这个修改后的示例中,try
子句(第3行和第4行)没有问题。代码将执行,except
子句将被跳过,并且程序将在try
和except
语句结束后继续执行。
Try子句中有错误并且指定了异常
如果try
子句中的代码确实引发异常,并且在任何except
关键字后指定了异常类型,程序将:
- 跳过
try
子句中剩余的代码 - 执行匹配的
except
子句中的任何代码 - 继续正常运行
x = 0
try:print(5 / x)
except:print("出错了")print("我在try子句之后执行!")# 出错了
# 我在try子句之后执行!
回到我的第一个例子,我将我们的变量x
改回了值0,并尝试将5除以x
。这会产生一个ZeroDivisionError
。由于我的except
语句指定了这种类型的异常,所以该子句中的代码在程序继续正常运行之前执行。
Try子句中有错误但未指定异常
最后,如果程序在try
子句中引发了异常,但在任何except
语句中未指定异常,那么程序将:
- 停止执行程序并抛出错误
x = 0
try:print(5 / y)
except:print("出错了")print("我在try子句之后执行!")# NameError: name 'y' is not defined
在上面的示例中,我试图将5除以变量y
,但该变量不存在。这会引发一个NameError
。我没有告诉程序如何处理NameError
,所以唯一的选择就是终止程序。
清理工作
Try
和except
是处理错误的主要工具,但你可以使用的一个可选子句是finally
。finally
子句将始终执行,无论是否发生错误。
x = 0
try:print(5 / x)
except ZeroDivisionError:print("我是except子句!")
finally:print("我是finally子句!")print("我在try子句之后执行!")# 我是except子句!
# 我是finally子句!
# 我在try子句之后执行!
在这个例子中,我创建了我们喜欢的ZeroDivisionError
。你可以看到执行顺序是:
except
子句finally
子句- 之后的任何代码
一旦我们修复try
子句不再引发错误,你仍然会看到类似的执行顺序。except
子句不再运行,try
子句将执行。
x = 1
try:print(5 / x)
except ZeroDivisionError:print("我是except子句!")
finally:print("我是finally子句!")print("我在try子句之后执行!")# 5.0
# 我是finally子句!
# 我在try子句之后执行!
你会注意到唯一的区别是try
子句成功执行,因为没有抛出异常。finally
子句和之后的代码会像你预期的那样执行。
这对于一些情况很有用,当你想要无论try
和except
子句的结果如何都要进行清理时。关闭连接、关闭文件和记录日志等操作都是finally
子句的很好候选对象。
使用Else子句进行条件执行
另一个可选子句是else
子句。else
子句很简单:如果try
子句中的代码执行时没有抛出错误,那么else
子句中的代码也将执行。
x = 1
try:print(5 / x)
except ZeroDivisionError:print("我是except子句!")
else:print("我是else子句!")
finally:print("我是finally子句!")print("我在try子句之后执行!")# 5.0
# 我是else子句!
# 我是finally子句!
# 我在try子句之后执行!
这个例子的执行顺序是:
try
子句else
子句finally
子句- 之后的任何代码
如果在try
子句中出现异常或错误,else
子句将被忽略。
x = 0
try:print(5 / x)
except ZeroDivisionError:print("我是except子句!")
else:print("我是else子句!")
finally:print("我是finally子句!")print("我在try子句之后执行!")# 我是except子句!
# 我是finally子句!
# 我在try子句之后执行!
内置异常
到目前为止,你已经看到我写了两种不同的命名异常:NameError
和ZeroDivisionError
。如果我需要其他异常呢?
Python标准库中有一整套异常列表。这些异常几乎可以满足你处理任何错误或异常的需求。
以下是一些可能很重要的异常:
KeyError
– 在字典中找不到键IndexError
– 在可迭代对象上索引超出范围TypeError
– 将函数或操作用于错误类型的对象OSError
– 一般的操作系统错误
还有很多其他的异常,可以在Python文档中找到。我鼓励你去看看。你不仅会更擅长处理错误,还会探索你的Python程序可能真正出错的地方。
自定义异常
如果你需要扩展功能,还可以定义自定义异常。
class FooError(Exception):def __init__(self, message):self.message = messagedef foo(self):print("bar")
在上面的示例中,我创建了一个新类,并将其扩展自异常类。现在,我可以编写自定义功能,并像对待其他对象一样处理此异常。
try:raise FooError("这是一个测试错误")
except FooError as e:e.foo()# bar
这里,我故意引发了我的新FooError
。我捕获了FooError
,并给它起了别名e
。现在,我可以访问我在创建的类中内置的foo()
方法。
这为处理错误提供了一系列可能性。自定义日志记录、更深入的跟踪或任何你需要的其他功能都可以编码和创建。
性能考虑
现在你已经了解了try
、except
和异常对象的基础知识,你可以开始考虑在你的代码中使用它们来优雅地处理错误。但是,代码性能会受到多大影响呢?
简短的答案是没有。随着Python 3.11的发布,当没有抛出异常时,使用try
和except
语句几乎不会减慢速度。
捕获错误确实会导致一些减速。但总的来说,捕获这些错误总比整个程序崩溃好。
在Python的早期版本中,使用try
和except
子句确实会导致一些额外的执行时间。如果你不是最新的,请记住这一点。
总结
感谢你阅读到这里。你的未来自己和客户会为你的错误处理感谢你。
我们讨论了try
、except
、else
和finally
子句及其执行顺序,以及在什么情况下它们被执行。我们还复习了创建自定义异常的基础知识。
最重要的是要记住,try
和except
子句是捕获错误的主要方式,你应该在有风险、容易出错的代码中使用它们。
此外,请记住,捕获错误将使你的代码更加弹性,并让你看起来更像一个优秀的程序员。
(本文视频讲解:java567.com)