Python3 面向对象编程笔记
第4章 异常捕获
在本章中将学习异常,特殊的错误对象只有在合理的时候才需要特别处理,将会学习:
- 如何找到异常出现的原因
- 遇到异常时如何恢复
- 如何以不同的方式处理不同的异常
- 遇到异常时如何清理
- 创建新的异常类型
- 在控制流中使用异常语法
抛出异常
本质上,异常只是一个对象,有很多不同的异常类,但是他们都继承子同一个异常类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
我们知道了如何处理异常,但是想要对不同的异常作出不同的反应,或者想要针对某种异常执行某些操作之后传递给上层函数,就像从来没有处理过一样,解决办法分别就是利用except和raise
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捕获其余的异常。
通常和捕获异常使用的还有as,else,finally
finally下的代码无论在什么条件下都会执行,如果我们需要在代码执行完成后执行特定的任务将非常有用,一些常见的例子:
- 清楚打开的数据库连接
- 关闭打开的文件
- 向网络发送一次关闭握手
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,但是除了下面这两个异常类:
- SystemExit,在程序自然退出时抛出,通常是在代码中调用了sys.exit函数,设计这个异常的目的是,在程序最终退出之前完成清理工作
- KeyboardInterrupt,常见于命令行程序,通常是ctrl + c
异常之间的层级关系:

当我们用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