美文网首页
关于Python异常处理,看这一篇就够了

关于Python异常处理,看这一篇就够了

作者: HackDev | 来源:发表于2020-06-11 14:52 被阅读0次

当我们用Python编程的时候,常常会出现很多错误,大多数是语法错误,当然也有一些语义错误。例如,我就经常忘记在if语句后面加冒号,然后运行的时候就会报错如下所示。

>>> if 5 % 2 == 1

  File "<stdin>", line 1

    if 5 % 2 == 1

                ^

SyntaxError: invalid syntax

如果我们使用Pycharm等IDE来写代码的时候,这种低级的语法错误会被IDE用红色波浪线标注出来,会比较方便我们解决这些错误。

但是,除了这些错误,我们写Python代码的时候也很可能包含一些逻辑上的错误,例如TypeError就是我们经常遇到的一种错误,它表示数据类型不匹配。

>>> "This year is " + 2020 + "."

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

TypeError: can only concatenate str (not "int") to str

例如上述代码从字面上理解是字符串的拼接,但是由于2020它是int整型,而不是一个字符串,不能应用于字符串的拼接。所以产生了TypeError异常。这种在Python代码执行过程中发生的非语法错误被称为异常。接下来,本文将介绍如何优雅地在Python中处理异常以及抛出异常。

异常处理的基本形式

处理异常的标准方法就是使用try...except语句。这一点其实比较类似于Java中的try...catch语句,事实上,大部分语言都有类似的捕捉异常的方法。

通常来说,可能产生异常的代码应该被try语句囊括进去,如果报异常的就会立即停止try语句中的剩余代码,并执行except语句中的代码。

我们可以看一个简单示例

>>> # Declare a function that can handle ZeroDivisionError

>>> def divide_twelve(number):

...    try:

...        print(f"Result: {12/number}")

...    except ZeroDivisionError:

...        print("You can't divide 12 by zero.")

...

>>> # Use the function

>>> divide_twelve(6)

Result: 2.0

>>> divide_twelve(0)

You can't divide 12 by zero.

我们都知道0不能做分母,因此在上述代码中,当0为分母时就产生了一个异常。于是就执行except语句中的代码了。

为什么要处理异常?

为什么要处理异常?因为未经处理的异常会直接中断Python程序的运行,举个例子,假如我们部署一个Python写的网站,其中一个用户的操作触发了某个Bug,导致程序产生了异常,Web服务就会直接被终止,所有用户都不能访问使用了,这显然会造成巨大损失。

>>> # Define a function without handling

>>> def division_no_handle(x):

...    print(f"Result: {20/x}")

...    print("division_no_handle completes running")

...

>>> # Define a function with handling

>>> def division_handle(x):

...    try:

...        print(f"Result: {20/x}")

...    except ZeroDivisionError:

...        print("You can't divide a number with zero.")

...    print("division_handle completes running")

...

>>> # Call the functions

>>> division_handle(0)

You can't divide a number with zero.

division_handle completes running

>>> division_no_handle(0)

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

  File "<stdin>", line 2, in division_no_handle

ZeroDivisionError: division by zero

通过上面的代码,我们也可以发现,当我们调用division_handle(0)时,由于异常得到了处理,程序还能继续运行,但是,当我们调用没有处理异常的division_no_handle(0)时,程序就直接中断了。

变量分配

我们可以将异常赋值给一个变量,从而进一步了解该异常的相关信息,方便对该异常进行处理。例如在下面代码中,我们可以将处理后的TypeError异常赋值给变量e,并将其打印出来。

>>> # A function that show error's detail

>>> def concat_messages(x, y):

...    try:

...        print(f"{x + y}")

...    except TypeError as e:

...        print(f"Argument Error: {e}")

...

>>> # Call the function

>>> concat_messages("Hello, ", 2020)

Argument Error: can only concatenate str (not "int") to str

当然,我们也可以一次捕捉多个异常,我们将可能的异常包装在一个元组中,如下面的代码片段中所示。当我们调用函数时,我们分别触发ValueError和ZeroDivisionError。可以看到两种异常都被捕获到了

>>> # A function that handles multiple exceptions

>>> def divide_six(number):

...    try:

...        formatted_number = int(number)

...        result = 6/formatted_number

...    except (ValueError, ZeroDivisionError) as e:

...        print(f"Error {type(e)}: {e}")

...

>>> # Use the function

>>> divide_six("six")

Error <class 'ValueError'>: invalid literal for int() with base 10: 'six'

>>> divide_six(0)

Error <class 'ZeroDivisionError'>: division by zero

异常处理语句

1,多异常语句

事实上,我们可以通过多个except子句来处理多个异常,每个子句都处理一些特定的异常。如下所示。

>>> # A function that has multiple except clauses

>>> def divide_six(number):

...    try:

...        formatted_number = int(number)

...        result = 6/formatted_number

...    except ValueError:

...        print("This is a ValueError")

...    except ZeroDivisionError:

...        print("This is a ZeroDivisionError")

...

>>> # Use the function

>>> divide_six("six")

This is a ValueError

>>> divide_six(0)

This is a ZeroDivisionError

2,else语句

我们也可以在try ... except代码块中使用else子句。不过需要注意的是,else子句需要出现在except子句之后。只有当try子句完成而没有引发任何异常时,else子句中的代码才会运行。如下例所示。

>>> # A function that has an else clause

>>> def divide_eight(number):

...    try:

...        result = 8/number

...    except:

...        print("divide_eight has an error")

...    else:

...        print(f"Result: {result}")

...

>>> # Use the function

>>> divide_eight(0)

divide_eight has an error

>>> divide_eight(4)

Result: 2.0

3,finally语句

finally子句放在块的最末尾,将在整个try…except块完成之后运行。只需要记住,无论是否产生异常,finally子句都会被执行就可以了,写法和else语句是一样的。

抛出异常

前面我们学习了使用try ... except块来处理Python中异常,接下来我们来看看抛出异常(产生)异常的基本形式。

>>> # Raise exceptions

>>> raise Exception

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

Exception

>>> raise NameError

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

NameError

>>> raise ValueError()

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

ValueError

如上所示,Python中使用raise关键字(Java中是throw关键字)后面跟上异常类(例如Exception,NameError)的方式来抛出异常。我们还可以使用异常类构造函数来创建实例,例如ValueError()。这两种用法没有区别,前者只是后者使用构造函数的语法糖。

1,自定义异常信息

我们还可以提供有关我们提出的异常的其他信息。最简单的方法是使用异常类构造函数,并包含适用的错误消息来创建实例。如下所示:

>>> raise ValueError("You can't divide something with zero.")

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

ValueError: You can't divide something with zero.

>>> raise NameError("It's silly to make this mistake.")

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

NameError: It's silly to make this mistake.

2,再抛出异常

如图所示,所有异常都会在被捕获时进行处理。但是,有可能我们可以重新抛出异常并将异常传递给外部范围,以查看是否可以处理该异常。

当我们编写涉及嵌套结构的复杂代码时(例如,一个函数调用另一个函数,该函数可能会调用另一个函数),此功能会更加有用。通过再抛出异常,我们可以决定在哪里处理特定异常。当然,根据具体情况确定处理特定异常的确切位置。我们先来看一些代码:

>>> # Define two functions with one calling the other

>>> def cast_number(number_text, to_raise):

...    try:

...        int(number_text)

...    except:

...        print("Failed to cast")

...        if to_raise:

...            print("Re-raise the exception")

...            raise

...

>>> def run_cast_number(number_text, to_raise):

...    try:

...        cast_number(number_text, to_raise)

...    except:

...        print("Handled in run_cast_number")

...

>>> # Use the functions

>>> run_cast_number("six", False)

Failed to cast

>>> run_cast_number("six", True)

Failed to cast

Re-raise the exception

Handled in run_cast_number

在上面的代码中,我们有两个函数,其中run_cast_number调用另一个函数cast_number。我们使用字符串两次调用该函数,这两个函数都会导致异常,因此将显示消息“ Fasted to cast”,因为该异常是在cast_number函数中处理的。但是,第二次调用函数,我们要求cast_number函数重新引发异常,以便except子句在run_cast_number函数中运行。

3,用户自定义异常

在许多情况下,我们可以使用内置的异常来帮助我们在项目中引发和处理异常。但是,Python使我们可以灵活地创建自己的自定义异常类。也就是说,我们需要将一个类声明为内置Exception类的子类。

>>> # Define a custom exception class

>>> class FileExtensionError(Exception):

... def __init__(self, filename, desired_ext):

... self.filename = filename

... self.desired_ext = desired_ext

... def __str__(self):

... return f"File {self.filename} should have the extension: {self.desired_ext}."

...

>>> # Raise custom exceptions

>>> raise FileExtensionError

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

TypeError: __init__() missing 2 required positional arguments: 'filename' and 'desired_ext'

>>> raise FileExtensionError("test.xls", "csv")

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

__main__.FileExtensionError: File test.xls should have the extension: csv.

如上所示,我们创建了一个名为FileExtensionError的自定义异常类。当我们抛出这个异常时,仅使用类名是行不通的。相反,我们应该通过为构造方法设置两个位置参数来实例化此异常。通过实现__str__方法,我们可以看到自定义异常消息。换句话说,异常消息是通过调用str()函数生成的。

4, 何时应该抛出异常

一般来说,当代码可能会在某些情况下会报错无法执行时,就应该抛出异常。

最后,再次安利一下我的公众号「会编程的Z同学」,会定期分享一些Python相关知识。

参考资料:https://medium.com/better-programming/how-to-handle-and-raise-exceptions-in-python-12-things-to-know-4dfef7f02e4

相关文章

网友评论

      本文标题:关于Python异常处理,看这一篇就够了

      本文链接:https://www.haomeiwen.com/subject/znebtktx.html