python應用開發實戰第二章 異常處理
2.1 異常與錯誤
-
錯誤
從軟體方面來說,錯誤是語法或是邏輯上的。錯誤是語法或是邏輯上的。 語法錯誤指示軟體的結構上有錯誤,導致不能被直譯器解釋或編譯器無法編譯。這些些錯誤必須在程式執行前糾正。當程式的語法正確後,剩下的就是邏輯錯誤了。邏輯錯誤可能是由於不完整或是不合法的輸入所致;在其它情況下,還可能是邏輯無法生成、計算、或是輸出結果需要的過程無法執行。這些錯誤通常分別被稱為域錯誤和範圍錯誤。當python檢測到一個錯誤時,python直譯器就會指出當前流已經無法繼續執行下去。這時候就出現了異常。
-
異常
對異常的最好描述是:它是因為程式出現了錯誤而在正常控制流。以外採取的行為。 這個行為又分為兩個階段:首先是引起異常發生的錯誤,然後是檢測(和採取可能的措施)階段。第一階段是在發生了一個異常條件(有時候也叫做例外的條件)後發生的。只要檢測到錯誤並且意識到異常條件,直譯器就會發生一個異常。引發也可以叫做觸發,丟擲或者生成。直譯器通過它通知當前控制流有錯誤發生。python也允許程式設計師自己引發異常。無論是python直譯器還是程式設計師引發的,異常就是錯誤發生的訊號。當前流將被打斷,用來處理這個錯誤並採取相應的操作。這就是第二階段。對於異常的處理髮生在第二階段,異常引發後,可以呼叫很多不同的操作。可以是忽略錯誤(記錄錯誤但不採取任何措施,採取補救措施後終止程式。)或是減輕問題的影響後設法繼續執行程式。所有的這些操作都代表一種繼續,或是控制的分支。關鍵是程式設計師在錯誤發生時可以指示程式如何執行。python用異常物件(exception object)來表示異常。遇到錯誤後,會引發異常。如果異常物件並未被處理或捕捉,程式就會用所謂的回溯(traceback)終止執行
2.2 異常處理
- try …except 語句
except後面如果沒有指定特定的異常型別,它將會捕捉到try語句中的所有型別的異常,如果後面指定特定的異常型別,可以利用該語句來專門處理該特定異常。
try:
#正常的操作
......................
except ExceptionType, Argument:
#你可以在這輸出 Argument 的值...
#每一次都只會有一個異常處理器被呼叫
def solve():
a=int(input('enter a number:'))
assert a>0 #斷言語句,如果a<=0,出現assertionerror
print ('number enter is Ok')
d=x+a
e=2*d
def func():
try:
solve()
except NameError as e: #如果a>0,執行這一句,因為x沒有被定義
print('number error',e.args)
except AssertionError:
print('assertionerror')#如果a<=0,執行這一句
if __name__=='__main__':
func()
上述的e是異常的例項化。通過args引數呼叫錯誤資訊
- raise關鍵字 raise用於強制讓異常發生。在下面的程式中,檢測到第三個excpet的異常後,執行完異常處理程式後,利用raise語句再次引發異常。
def solve():
b=0
a=int(input('enter a number:'))
assert a>0
print ('number enter is Ok')
a+=a/b
d=x+a
e=2*d
def func():
try:
solve()
except NameError as e:
print('number error',e.args)
except AssertionError:
print('assertionerror')
except Exception as e :
print('unhandle error ,logging thr error ')
print(e.args)
raise
if __name__=='__main__':
func()
-
try .,.except的else程式碼塊 else語句緊跟在try…except後,如果try程式碼塊下的語句沒有異常才會執行。else程式碼塊會在finally語句之前執行。
-
finally… 無論是否有異常被丟擲,都會執行。相當於是一種全天候保障。
2.3 獸人之襲V1.1.0 異常處理
在上一篇程式碼的基礎上,我們使用了抽象基類AbstractGameUnit代替gameunit。並且針對使用者輸入增加了異常處理的程式碼。主要分佈在gameunit的heal函式中一起process_users_choice函式中。
# python應用開發實戰
# 獸人之襲v1.1.0.面向物件程式設計
'''
需求分析:
針對使用者的輸出加入異常處理
'''
import random
import textwrap
import sys
from abc import ABCMeta, abstractmethod
if sys.version_info < (3, 0):
print('本程式碼需要在python3.5以上的版本執行')
print('當前版本號為%d.%d' % (sys.version_info[0], sys.version_info[1]))
print('正在退出')
sys.exit(1)
def print_dotted_line(width=72):
print('-' * width)
def show_theme_message(width=72):
print_dotted_line()
print('\033[1;35;34m' + '獸人之襲V0.0.1:' + '\033[0m')
# ---------------------------------------
# 輸出字型顏色格式的更改
# 1:高亮顯示,35:前景色為紫紅色,34:背景色為藍色
# 格式為‘\033[顯示方式;前景色;背景色 +'text' +'\033[0m'
msg = (
'人類和他們的敵人——獸人之間的戰爭即將開始,'
'foo爵士,守衛在南部平原的勇敢騎士之一,開'
'始了一段漫長的旅途。在一個未知的茂密的森'
'林,他發現了一個小的孤立居住點,因為'
'疲勞的原因,再加上希望能補充到糧食儲備,他'
'決定繞道而行。當他走進村莊時,他看見5個木屋'
',周圍沒有任何敵人。猶豫之後,他決定走進其中'
'一間木屋......'
)
print(textwrap.fill(msg, width=width // 2)) # 調整輸出格式,以填充的形式輸出
def weighted_random_selection(obj1, obj2):
weighted_list = 4* [id(obj1)] + 6 * [id(obj2)]
selction = random.choice(weighted_list)
if selction == id(obj1):
return obj1
else:
return obj2
def print_bold(msg, end='\n'):
print('\033[1m' + msg + '\033[0m', end=end)
class AbstractGameUnit(metaclass=ABCMeta):
def __init__(self, name=''):
self.max_hp = 0
self.health_meter = 0
self.name = name
self.enemy = None
self.unit_type = None
@abstractmethod
def info(self):
"""Information on the unit (MUST be overridden in subclasses)"""
pass
def attack(self, enemy):
injured_unit = weighted_random_selection(self, enemy)
injury = random.randint(10, 15)
injured_unit.health_meter = max(injured_unit.health_meter - injury, 0)
print("攻擊! ", end='')
self.show_health(end=' ')
enemy.show_health(end=' ')
def heal(self, heal_by=2, full_healing=True):
if self.health_meter == self.max_hp:
return
if full_healing:
self.health_meter = self.max_hp
else:
self.health_meter += heal_by
print_bold("你已經被治療!", end=' ')
self.show_health(bold=True)
def reset_health_meter(self):
self.health_meter = self.max_hp
def show_health(self, bold=False, end='\n'):
msg = "Health: %s: %d" % (self.name, self.health_meter)
if bold:
print_bold(msg, end=end)
else:
print(msg, end=end)
class knight(AbstractGameUnit):
def __init__(self, name='Foo先生'):
super().__init__(name=name) # 呼叫超類的初始化函式
self.max_hp = 40
self.health_meter = self.max_hp
self.unit_type = '朋友'
def info(self):
print('我是一名騎士')
def acquire_hut(self, hut):
print_bold('進入%d號木屋' % hut.number, end='')
is_enemy = (isinstance(hut.occupant, AbstractGameUnit) and hut.occupant.unit_type == '敵人')
continue_attack = 'y'
if is_enemy:
print_bold('發現敵人!')
self.show_health(bold=True, end=' ')
hut.occupant.show_health(bold=True, end='')
while continue_attack:
try:
continue_attack = input('是否繼續攻擊?(y/n): ')
assert continue_attack=='y' or continue_attack=='n' #處理使用者輸出不是y/n的情況
if continue_attack == 'n':
self.runaway()
break
self.attack(hut.occupant)
if hut.occupant.health_meter <= 0:
print(' ')
hut.acquire(self)
break
if self.health_meter <= 0:
print(' ')
break
except AssertionError:
print('錯誤!請輸入y或者n')
continue
else:
if hut.get_occupant_type() == '無人居住':
print_bold('木屋無人居住')
else:
print_bold('找到一個朋友')
hut.acquire(self)
self.heal()
def runaway(self):
print_bold('溜了溜了...')
self.enemy = None
class hut():
def __init__(self, number, occupant):
self.occupant = occupant
self.number = number
self.is_acquired = False # 木屋是否被調查
def acquire(self, new_occupant):
self.occupant = new_occupant
self.is_acquired = True
print_bold('幹得好!,%d號木屋已經被調查' % self.number)
def get_occupant_type(self):
if self.is_acquired:
occupant_type = '已被調查'
elif self.occupant is None:
occupant_type = '無人居住'
else:
occupant_type = self.occupant.unit_type
return occupant_type
class orcrider(AbstractGameUnit):
def __init__(self, name=''):
super().__init__(name=name)
self.max_hp = 30
self.health_meter = self.max_hp
self.unit_type = '敵人'
self.hut_number = 0
def info(self):
print('啊啊啊啊!獸人永不為奴!別惹我')
class attackoftheorcs:
def __init__(self):
self.huts = []
self.player = None
def get_occupants(self):
return [x.get_occupant_type() for x in self.huts]
def show_game_mission(self):
print_bold('任務1:')
print('1、打敗所有敵人')
print('2、調查所有木屋,獲取所有木屋的控制權')
print_dotted_line()
def process_user_choice(self):
verifying_choice = True
idx = 0
print("木屋調查情況: %s" % self.get_occupants())
while verifying_choice:
user_choice = input('選擇一個木屋進入(1-5):')
try:
idx=int(user_choice)
except ValueError as e : #處理使用者輸出不是數字的異常
print('非法輸入,錯誤資訊:%s\n'%e.args)
continue
try: #處理使用者輸出小於等於0的異常
idx=int(user_choice)
assert idx>0
except AssertionError:
print('錯誤!請輸入大於0的數值')
continue
try:
if self.huts[idx-1].is_acquired:
print ('你已經調察過這個木屋,請再嘗試一次'
'提示:不能再已調查的木屋得到治療')
else:
verifying_choice=False
except IndexError: #處理使用者輸入數值超過範圍的異常
print('無效的輸入:',idx)
print('輸出的數字範圍必須在0~5之間,請在嘗試一次')
continue
return idx
def occupy_huts(self):
for i in range(5):
choice_list = ['敵人', '朋友', None]
computer_choice = random.choice(choice_list)
if computer_choice == '敵人':
name = '敵人-' + str(i + 1)
self.huts.append(hut(i + 1, orcrider(name)))
elif computer_choice == '朋友':
name = '騎士-' + str(i + 1)
self.huts.append(hut(i + 1, knight(name)))
else:
self.huts.append(hut(i + 1, computer_choice))
def play(self):
self.player = knight()
self.occupy_huts()
acquires_hut_counter = 0
self.show_game_mission()
self.player.show_health(bold=True)
while acquires_hut_counter < 5:
idx = self.process_user_choice()
self.player.acquire_hut(self.huts[idx - 1])
if self.player.health_meter <= 0:
print_bold('你輸了!祝下次好運!')
break
if self.huts[idx - 1].is_acquired:
acquires_hut_counter += 1
if acquires_hut_counter == 5:
print_bold('祝賀你!,你已經調查完所有的木屋')
if __name__ == '__main__':
game = attackoftheorcs()
game.play()
2.4 自定義異常
自定義一個gameuniterror異常類:
class Gameuniterror(Exception):
def __init__(self,message='',code=000):
super().__init__(message)
self.error_message='~'*50+'\n'
self.error_dict={
000:'error-000:unspecified error',
101:'error-101:health meter problem',
102:'error-102:attack issue ignored'
}
try:
self.error_message+=self.error_dict[code]
except KeyError:
self.error_message+=self.error_dict[000]
self.error_message+='\n'+'~'*50
這裡我們定義了一個gameuniterror異常類,繼承自Exception類。該異常類可以傳輸引數code,用來表徵不同種類的異常。 在gameunit類中定義的治療heal函式我們做出如下的修改:
from gameuniterror import GameUnitError
def heal(self, heal_by=100, full_healing=False):
if self.health_meter == self.max_hp:
return
if full_healing:
self.health_meter = self.max_hp
else:
self.health_meter += heal_by
if self.health_meter>self.max_hp:
raise Gameuniterror(message='health_meter>max_hp',code=101)
print_bold("你已經被治療!", end=' ')
self.show_health(bold=True)
新建一個.py檔案:
from attackoforchs import knight
from gameuniterror import Gameuniterror
if __name__=='__main__':
print ('--------------')
knight=knight('sir ball')
knight.health_meter=10
knight.show_health()
try:
knight.heal(heal_by=100)
except Gameuniterror as e:
print (e) #注意這裡輸出的是message引數
print(e.error_message)#這裡輸出的是error_message引數
輸出結果: Health: sir ball: 10 health_meter>max_hp ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error-101:health meter problem ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ print (e)輸出的是在基類Exception中初始化的message引數,如果沒有進行初始化,則不輸出任何東西。 如果異常種類較多,有兩種處理方法:一是增加error.dict的關鍵字,二是通過建立以gameuniterror為父類的子類,該子類可以只表示某一種特定的異常。