《Python3 面向对象编程》笔记 - 第4章 异常捕获

Python3 面向对象编程笔记

第4章 异常捕获

在本章中将学习异常,特殊的错误对象只有在合理的时候才需要特别处理,将会学习:

  1. 如何找到异常出现的原因
  2. 遇到异常时如何恢复
  3. 如何以不同的方式处理不同的异常
  4. 遇到异常时如何清理
  5. 创建新的异常类型
  6. 在控制流中使用异常语法

抛出异常

本质上,异常只是一个对象,有很多不同的异常类,但是他们都继承子同一个异常类BaseException。一些常见的异常:

In [1]: x = 5 / 0
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-1-663c7a933a87> in <module>()
----> 1 x = 5 / 0

ZeroDivisionError: division by zero

In [2]: lst = [1]

In [3]: print(lst[3])
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-3-7a094246b9ab> in <module>()
----> 1 print(lst[3])

IndexError: list index out of range

In [4]: lst + 2
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-ffe4e6e220bf> in <module>()
----> 1 lst + 2

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

In [5]: lst.add
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-5-50e4efd52cad> in <module>()
----> 1 lst.add

AttributeError: 'list' object has no attribute 'add'

In [6]: d = {'a':'b'}

In [7]: d['df']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-7-4b3d04097323> in <module>()
----> 1 d['df']

KeyError: 'df'

python中大部分的错误类都继承自 Exception(它又继承自 BaseException )

抛出一个异常

当我们的程序比如遇到不合法的输入时,需要抛出异常可以像下面这样,利用 raise

In [2]: class EvenOnly(list):
   ...:
   ...:     def append(self, integer):
   ...:         if not isinstance(integer, int):
   ...:             raise TypeError("Only integers can be added")
   ...:         if integer % 2:
   ...:             raise ValueError("Only even numbers can be added")
   ...:         super().append(integer)
   ...:

In [3]: e = EvenOnly()

In [4]: e.append("a string")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-9cff98c34dae> in <module>()
----> 1 e.append("a string")

<ipython-input-2-7af07cf6ebb7> in append(self, integer)
      3     def append(self, integer):
      4         if not isinstance(integer, int):
----> 5             raise TypeError("Only integers can be added")
      6         if integer % 2:
      7             raise ValueError("Only even numbers can be added")

TypeError: Only integers can be added

In [5]: e.append(3)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-5-d9ea7f347873> in <module>()
----> 1 e.append(3)

<ipython-input-2-7af07cf6ebb7> in append(self, integer)
      5             raise TypeError("Only integers can be added")
      6         if integer % 2:
----> 7             raise ValueError("Only even numbers can be added")
      8         super().append(integer)
      9

ValueError: Only even numbers can be added

异常的作用

当抛出一个异常时,该异常后面所有的代码都将不会执行


def no_return():
    print("I am about to raise an exception")
    raise Exception("This is always raised")
    print("This line will never execute")


def call_exception():
    print("call_exception starts here...")
    no_return()
    print("an exception was raised...")


if __name__ == "__main__":
    call_exception()

# call_exception starts here...
# I am about to raise an exception
# Traceback(most recent call last):
#   File "test.py", line 15, in < module >
#   call_exception()
#   File "test.py", line 10, in call_exception
#   no_return()
#   File "test.py", line 4, in no_return
#   raise Exception("This is always raised")
# Exception: This is always raised

处理异常

当我们需要处理一个异常时,可以像下面这样:

def funy_division2(anumber):
    try:
        if anumber == 13:
            raise ValueError("13 is an unlucky number")
        return 100 / anumber
    except (ZeroDivisionError, TypeError):
        return "Enter a number other than zero"


for val in (0, "hello", 50.0, 13):
    # print end设定尾部的符号
    print(f"Testiong {val}", end=" ")
    print(funy_division2(val))

# Testiong 0 Enter a number other than zero
# Testiong hello Enter a number other than zero
# Testiong 50.0 2.0
# Testiong 13 Traceback(most recent call last):
#   File "test.py", line 13, in < module >
#   print(funy_division2(val))
#   File "test.py", line 4, in funy_division2
#   raise ValueError("13 is an unlucky number")
# ValueError: 13 is an unlucky number

我们知道了如何处理异常,但是想要对不同的异常作出不同的反应,或者想要针对某种异常执行某些操作之后传递给上层函数,就像从来没有处理过一样,解决办法分别就是利用exceptraise

def funy_division2(anumber):
    try:
        if anumber == 13:
            raise ValueError("13 is an unlucky number")
        return 100 / anumber
    except ZeroDivisionError:
        return "Enter a number other than zero"
    except TypeError:
        return "Enter a numberical value"
    except ValueError:
        print("No, No, not 13!")
        raise


for val in (0, "hello", 50.0, 13):
    # print end设定尾部的符号
    print(f"Testiong {val}", end=" ")
    print(funy_division2(val))

# Testiong 0 Enter a number other than zero
# Testiong hello Enter a numberical value
# Testiong 50.0 2.0
# Testiong 13 No, No, not 13!
# Traceback(most recent call last):
#   File "test.py", line 18, in < module >
#   print(funy_division2(val))
#   File "test.py", line 4, in funy_division2
#   raise ValueError("13 is an unlucky number")
# ValueError: 13 is an unlucky number

如果我们在捕获TypeError之前,捕获了Exception,那么就只有捕获Exception的代码执行。

利用上述的特性,我们可以在处理完一个特殊的异常后,最后统一用Exception捕获其余的异常。

通常和捕获异常使用的还有aselsefinally

finally下的代码无论在什么条件下都会执行,如果我们需要在代码执行完成后执行特定的任务将非常有用,一些常见的例子:

  1. 清楚打开的数据库连接
  2. 关闭打开的文件
  3. 向网络发送一次关闭握手

finally语句对于我们在try中执行return语句也非常重要,finally中的代码任然会在返回值之前执行

比如:

def test():
    try:
        10 / 0
        print('10/0')
    except ZeroDivisionError:
        print('0')
        return 1
        print('1')
    finally:
        print('finally exec')

test()

# 0
# finally exec

异常的层级

大部分的异常类都继承自Exception,但是除了下面这两个异常类:

  1. SystemExit,在程序自然退出时抛出,通常是在代码中调用了sys.exit函数,设计这个异常的目的是,在程序最终退出之前完成清理工作
  2. KeyboardInterrupt,常见于命令行程序,通常是ctrl + c

异常之间的层级关系:

第4章-异常层级

当我们用except:从句而不添加任何类型的异常时,将会捕获所有BaseException的子类,也就是会捕获所有异常

定义我们的异常

异常类的名字通常用于说明发生了什么错误,而且可以先初始化函数中传入任何参数来提供额外的信息

通常我们自定义的异常类继承Exception,而不是BaseException,因为BaseException无法被except Exception从句捕获

Exception.__init__方法设计成接受任意参数并将它们作为一个元组保存在一个名为args的属性当中,这使得我们可以更容易的定义新的异常,而不需要重写__init__方法

比如:

class InvalidWithdrawal(Exception):
    def __init__(self, balance, amount):
        super().__init__(f"account doesn't have {amount}")
        self.balance = balance
        self.amount = amount

    def overage(self):
        return self.amount - self.balance


print(InvalidWithdrawal(25, 20).args)
# ("account doesn't have 20",)

raise InvalidWithdrawal(25, 20)
# Traceback(most recent call last):
#   File "test.py", line 13, in < module >
#   raise InvalidWithdrawal(25, 20)
# __main__.InvalidWithdrawal: account doesn't have 20

当我们需要处理异常时可以这样:

class InvalidWithdrawal(Exception):
    def __init__(self, balance, amount):
        super().__init__(f"account doesn't have {amount}")
        self.balance = balance
        self.amount = amount

    def overage(self):
        return self.amount - self.balance


try:
    raise InvalidWithdrawal(25, 50)
except InvalidWithdrawal as e:
    print(f"catch err {e.overage()}")

可以像对待其他对象一样对待异常类,可以为他添加属性和方法

使用自定义异常的真正优势在于创建供他人使用的框架、库或者API上

Python程序员倾向于追随“请求谅解,而不是许可”的原则,也就是说,他们先执行代码,然后解决错误。认为没有必要去花费cpu资源去检查一些很少才会出现的情况。比如下面的两段代码:

def divide_with_exception(number, divisor):
    try:
        number / divisor
    except ZeroDivisionError:
        print("You can't divide by zero")


def divide_with_if(number, divisor):
    if divisor == 0:
        print("You can't divide by zero")
    else:
        number / divisor

两段代码都是可以执行的,但是Python程序员应该更倾向于写第一种方式的代码

案例

import hashlib


class User:

    def __init__(self, username, password):
        self.username = username
        self.password = self._encrypt_pw(password)
        self.is_logged_in = False

    def _encrypt_pw(self, password):
        hash_string = (self.username + password)
        hash_string = hash_string.encode("utf-8")
        return hashlib.sha256(hash_string).hexdigest()

    def check_password(self, password):
        encrypted = self._encrypt_pw(password)
        return encrypted == self.password


class AuthException(Exception):

    def __init__(self, username, user=None):
        super().__init__(username, user)
        self.username = username
        self.user = user


class UsernameAlreadyExists(AuthException):
    pass


class PasswordTooShort(AuthException):
    pass


class Authenticator:

    def __init__(self):
        self.users = {}

    def add_user(self, username, password):
        if username in self.users:
            raise UsernameAlreadyExists(username)
        if len(password) < 6:
            raise PasswordTooShort(username)
        self.users[username] = User(username, password)

    def login(self, username, password):
        try:
            user = self.users[username]
        except KeyError:
            raise InvalidUsername(username)

        if not user.check_password(password):
            raise InvalidPassword(username, user)

        user.is_logged_in = True
        return True

    def is_logged_in(self, username):
        if username in self.users:
            return self.users[username].is_logged_in
        return False


class InvalidUsername(AuthException):
    pass


class InvalidPassword(AuthException):
    pass


class Authorizor:

    def __init__(self, authenticator):
        self.authenticator = authenticator
        self.permissions = {}

    def add_permission(self, perm_name):
        try:
            perm_set = self.permissions[perm_name]
        except KeyError:
            self.permissions[perm_name] = set()
        else:
            raise PermissionError("Permission Exists")

    def permit_user(self, perm_name, username):
        try:
            perm_set = self.permissions[perm_name]
        except KeyError:
            raise PermissionError("Permission does not exist")
        else:
            if username not in self.authenticator.users:
                raise InvalidUsername(username)
            perm_set.add(username)

    def check_permission(self, perm_name, username):
        if not self.authenticator.is_logged_in(username):
            raise NotLoggedInError(username)
        try:
            perm_set = self.permissions[perm_name]
        except KeyError:
            raise PermissionError("Permission does not exist")
        else:
            if username not in perm_set:
                raise NotPermittedError(username)
            else:
                return True


class PermissionError(Exception):
    pass


class NotLoggedInError(AuthException):
    pass


class NotPermittedError(AuthException):
    pass


authenticator = Authenticator()
authorizor = Authorizor(authenticator)

authenticator.add_user("joe", "joepassword")

authorizor.add_permission("paint")

authorizor.check_permission("paint", "joe")
# Traceback (most recent call last):
#   File "test.py", line 129, in <module>
#     authorizor.check_permission("paint", "joe")
#   File "test.py", line 101, in check_permission
#     raise NotLoggedInError(username)
# __main__.NotLoggedInError: ('joe', None)

print(authenticator.is_logged_in("joe"))
# False

print(authenticator.login("joe", "joepassword"))

authorizor.check_permission("paint", "joe")
# Traceback (most recent call last):
#   File "test.py", line 145, in <module>
#     authorizor.check_permission("paint", "joe")
#   File "test.py", line 108, in check_permission
#     raise NotPermittedError(username)
# __main__.NotPermittedError: ('joe', None)

authorizor.check_permission("mix", "joe")
# Traceback (most recent call last):
#   File "test.py", line 103, in check_permission
#     perm_set = self.permissions[perm_name]
# KeyError: 'mix'

# During handling of the above exception, another exception occurred:

# Traceback (most recent call last):
#   File "test.py", line 153, in <module>
#     authorizor.check_permission("mix", "joe")
#   File "test.py", line 105, in check_permission
#     raise PermissionError("Permission does not exist")
# __main__.PermissionError: Permission does not exist

authorizor.permit_user("paint", "joe")
print(authorizor.check_permission("paint", "joe"))
# True

   转载规则


《《Python3 面向对象编程》笔记 - 第4章 异常捕获》 KaKa 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录