1. 程式人生 > >python應用開發實戰第二章 異常處理

python應用開發實戰第二章 異常處理

2.1 異常與錯誤

  • 錯誤

    從軟體方面來說,錯誤是語法或是邏輯上的。錯誤是語法或是邏輯上的。 語法錯誤指示軟體的結構上有錯誤,導致不能被直譯器解釋或編譯器無法編譯。這些些錯誤必須在程式執行前糾正。當程式的語法正確後,剩下的就是邏輯錯誤了。邏輯錯誤可能是由於不完整或是不合法的輸入所致;在其它情況下,還可能是邏輯無法生成、計算、或是輸出結果需要的過程無法執行。這些錯誤通常分別被稱為域錯誤和範圍錯誤。當python檢測到一個錯誤時,python直譯器就會指出當前流已經無法繼續執行下去。這時候就出現了異常。

  • 異常

    對異常的最好描述是:它是因為程式出現了錯誤而在正常控制流。以外採取的行為。 這個行為又分為兩個階段:首先是引起異常發生的錯誤,然後是檢測(和採取可能的措施)階段。第一階段是在發生了一個異常條件(有時候也叫做例外的條件)後發生的。只要檢測到錯誤並且意識到異常條件,直譯器就會發生一個異常。引發也可以叫做觸發,丟擲或者生成。直譯器通過它通知當前控制流有錯誤發生。python也允許程式設計師自己引發異常。無論是python直譯器還是程式設計師引發的,異常就是錯誤發生的訊號。當前流將被打斷,用來處理這個錯誤並採取相應的操作。這就是第二階段。對於異常的處理髮生在第二階段,異常引發後,可以呼叫很多不同的操作。可以是忽略錯誤(記錄錯誤但不採取任何措施,採取補救措施後終止程式。)或是減輕問題的影響後設法繼續執行程式。所有的這些操作都代表一種繼續,或是控制的分支。關鍵是程式設計師在錯誤發生時可以指示程式如何執行。python用異常物件(exception object)來表示異常。遇到錯誤後,會引發異常。如果異常物件並未被處理或捕捉,程式就會用所謂的回溯(traceback)終止執行

    常見的異常列表:python 官方文件 中文描述

2.2 異常處理

  • try …except 語句
Created with Raphaël 2.2.0開始執行try語句發生異常?執行except處理異常結束yesno

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為父類的子類,該子類可以只表示某一種特定的異常。