2.1 异常与错误
-
错误
从软件方面来说,错误是语法或是逻辑上的。错误是语法或是逻辑上的。
语法错误指示软件的结构上有错误,导致不能被解释器解释或编译器无法编译。这些些错误必须在程序执行前纠正。当程序的语法正确后,剩下的就是逻辑错误了。逻辑错误可能是由于不完整或是不合法的输入所致;在其它情况下,还可能是逻辑无法生成、计算、或是输出结果需要的过程无法执行。这些错误通常分别被称为域错误和范围错误。当python检测到一个错误时,python解释器就会指出当前流已经无法继续执行下去。这时候就出现了异常。 -
异常
对异常的最好描述是:它是因为程序出现了错误而在正常控制流。以外采取的行为。 这个行为又分为两个阶段:首先是引起异常发生的错误,然后是检测(和采取可能的措施)阶段。第一阶段是在发生了一个异常条件(有时候也叫做例外的条件)后发生的。只要检测到错误并且意识到异常条件,解释器就会发生一个异常。引发也可以叫做触发,抛出或者生成。解释器通过它通知当前控制流有错误发生。python也允许程序员自己引发异常。无论是python解释器还是程序员引发的,异常就是错误发生的信号。当前流将被打断,用来处理这个错误并采取相应的操作。这就是第二阶段。对于异常的处理发生在第二阶段,异常引发后,可以调用很多不同的操作。可以是忽略错误(记录错误但不采取任何措施,采取补救措施后终止程序。)或是减轻问题的影响后设法继续执行程序。所有的这些操作都代表一种继续,或是控制的分支。关键是程序员在错误发生时可以指示程序如何执行。python用异常对象(exception object)来表示异常。遇到错误后,会引发异常。如果异常对象并未被处理或捕捉,程序就会用所谓的回溯(traceback)终止执行
常见的异常列表:python 官方文档
中文描述
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为父类的子类,该子类可以只表示某一种特定的异常。