28. 企业级开发基础9:异常处理

本节内容如下:

  1. 什么是异常,对异常的解释和描述,口语描述和专业术语的联系
  • 代码中出现错误的处理手段
  • 异常处理方式
    • 什么样的情况算异常
    • 捕获异常【try-except-else-finally】
    • 抛出异常【raise】
  • 常见异常

1. 什么是异常

我们程序在开发过程中,总会遇到各种各样的一些问题,有些是由于拼写、配置、选项等等各种引起的程序错误,有些是由于程序功能处理逻辑不完善引起的漏洞,这些统称为我们程序中的异常

所谓异常:就是不正常的情况,错误和漏洞都是不正常的情况,异常情况有时候也会称呼为BUG,也就是缺陷、漏洞的意思,程序执行过程中出现异常会影响程序的正常执行。

python中内置了一整套完善的异常处理机制,可以让开发人员快速针对出现问题的代码进行完善和处理。

我们针对python可能遇到的不同的异常情况,一般会做如下处理:

  • 如果是拼写、配置等等引起的错误,根据出错信息进行排查错误出现的位置进行解决
  • 如果是程序设计不完善引起的漏洞,根据漏洞的情况进行设计处理漏洞的逻辑;
    切记:合理的处理BUG也是程序设计开发的一部分

2. 错误处理

错误的出现,在程序中一般会有两种表现,一种是拼写错误,一种是程序执行过程中出现的错误,这样两种不同的错误应该怎么进行追踪和处理呢?

2.1. 拼写错误

常规情况下,拼写错误只是在简单的记事本等环境下进行开发时,容易手误产生拼写错误;当前开发环境下,我们经常使用一些半自动化的IDE开发工具,如pycharm等等,可以进行简单的程序关键字的拼写检查以及程序结构的检查,把一些简单的拼写问题掐死在萌芽之中

程序设计开发的学习需要经历一个过程,建议开始的基础部分使用超级记事本进行开发,如editplus、ultraedit、sublime等等,对于基础的掌握会有一个非常不错的提升作用;进入后续的企业级项目开发阶段之后可以使用高级开发工具来提升我们的开发效率,如Pycharm、eclipse等等。

2.2. 程序运行时错误

程序运行过程中,也会出现各种各样的错误,对于错误的出现和提示信息必须有一个比较明确的掌握,才能在后续的程序开发中快速的开发并且修复问题,这里就会出现两个步骤

  • 确定问题及问题出现的代码行
  • 后续的问题处理【参考后面的异常处理】

首先我们必须查询问题出现的错误提示信息,观察如下代码

# 程序测试
class Person(object):
    # 通过__slots__属性定义可以扩展的成员属性名称
    __slots__ = ("__name", "address", "age", "gender", "email")

    # 初始化方法
    def __init__(self, name):
        self.__names = name

    # 属性的set/get方法
    def get_name(self):
        return self.__name

    def set_name(self, name):
        self.__name = name


# 创建对象
p = Person("tom")
print(p.get_name())

这里我们使用的开发工具是PyCharm,代码开发过程中,必须时刻观察我们的编辑工具是否出现错误提示,如果出现错误提示就是关键字拼写问题或者程序结构设计问题,需要及时修改;上面的代码开发工具没有报错,那就直接运行代码,出现如下结果:

Traceback (most recent call last):
  File "D:/resp_work/PY_WORK/备课/模块化开发/demo04/demo10.py", line 18, in <module>
    p = Person("tom")
  File "D:/resp_work/PY_WORK/备课/模块化开发/demo04/demo10.py", line 7, in __init__
    self.__names = name
AttributeError: 'Person' object has no attribute '_Person__names'

运行结果中出现了错误,错误的名称是AttributeError,错误的提示是'Person' object has no attribute '_Person__names',简单翻译过来就是在Person对象中没有属性_Person__names
仅仅依靠这样的错误提示,我们已经了解到,可能是我们对象的属性操作过程中出现了什么错误,到底出现了什么错误呢?继续观察上面的错误代码:
从错误的第一行代码

Traceback (most recent call last):

这行代码的意思是跟踪错误的出现的过程,查看跟踪提示信息下面的第一行错误提示:

File "D:/resp_work/PY_WORK/备课/模块化开发/demo04/demo10.py", line 18, in <module>
    p = Person("tom")

首先在文件D:/resp_work/PY_WORK/备课/模块化开发/demo04/demo10.py的第8行出现了错误,错误代码是p = Person("tom"),这里是错误开始的地方,明显这里的代码没有什么错误,那就接着往下看

  File "D:/resp_work/PY_WORK/备课/模块化开发/demo04/demo10.py", line 7, in __init__
    self.__names = name

在文件D:/resp_work/PY_WORK/备课/模块化开发/demo04/demo10.py的第7行line 7出现的错误,主要代码是self.__names = name,看到这里,我们已经明确,是在我们程序的__init__(self, name)初始化方法中,写错了我们的属性名称,属性名称本意设置的是__name但是错误写成了__names,修改代码如下

# 程序测试
class Person(object):
    # 通过__slots__属性定义可以扩展的成员属性名称
    __slots__ = ("__name", "address", "age", "gender", "email")

    # 初始化方法
    def __init__(self, name):
        self.__name = name

    # 属性的set/get方法
    def get_name(self):
        return self.__name

    def set_name(self, name):
        self.__name = name


# 创建对象
p = Person("tom")
print(p.get_name())

执行代码,出现如下结果:

tom

说明错误正常处理了。

解决程序中遇到错误的核心操作
核心操作其实就是定位错误出现的行号,然后根据对代码执行前后的简单分析来定位出现错误的地方,简单的错误就可以直接修复;当然,某些情况下如果出现运行过程中可能会出现的错误,就是程序中的异常了,对于异常的处理,请参考后面的异常处理部分。

3. 异常处理

所谓异常,是程序执行过程中,出现了不正常的情况影响了整个程序的正常执行
所谓处理异常,就是先通过指定的条件捕获异常,捕获到异常之后进行后续的处理,以正常的情况提示并处理发生的异常,让程序正常的执行的过程
python中出现的所有的异常,都是直接或者间接继承自BaseException这个类的

3.1. 代码中什么样的情况是异常?

python提供了一套try-except-finally的异常处理代码块,用于针对可能出现问题的代码进行容错和处理

异常处理的语法结构如下:

try:
    <正常要执行的代码语句,执行过程中可能会出现异常>
except <异常名称>:
    <对应异常的处理代码>
else:
    <如果没有异常,执行的后续代码>
finally:
    <无论是否出现异常,最终都会执行的代码>

接下来,观察下面这段代码的设计和执行过程,你能发现问题出现在哪里吗?

# 常规情况下的几行代码:计算两个数的加法运算
def add():
    num1 = int(input("请输入第一个数字:"))
    num2 = int(input("请输入第二个数字:"))
    num3 = num1 + num2
    print("两个数字计算的结果是:" +  str(num3))
# 调用函数开始计算
add()
# 执行结果
~请输入第一个数字:12
~请输入第二个数字:10
~两个数字计算的结果是:22

上述功能的程序设计时,已经考虑了诸多的问题,如用户输入的数据应该是字符串,代码中通过int()方法进行了强制类型转换,在最后输出数据的时候,由于num3是数值,数值和字符串不能直接用符号+连接,所以对num3又通过str()函数强制转换成了字符串。正常情况下,程序没有任何问题。

但是上述程序的缺陷并非正常流程下,而是如果用户在应该输入数字的情况下,输入了字母或者其他的非数字字符,程序就出现错误了,这个才是我们要解决的程序的BUG

>>> add()
请输入第一个数字:ab
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in add
ValueError: invalid literal for int() with base 10: 'ab'

我们对上述函数进行如下改造:

# 常规情况下的几行代码:计算两个数的加法运算
def add():
    try:
        num1 = int(input("请输入第一个数字:"))
        num2 = int(input("请输入第二个数字:"))
        num3 = num1 + num2
        print("两个数字计算的结果是:" +  str(num3))
    except:
        print("您输入的数值非法,只能输入整数")
 
# 调用函数开始计算:执行过程如下
>>> add()
请输入第一个数字:12
请输入第二个数字:13
两个数字的和:25
>>> add()
请输入第一个数字:ab
您输入了非法的非数字字符

可以看到,上面通过添加try-except这样的一个代码块,完美的解决了我们出现的错误,不至于让错误导致程序的崩溃

3.2. 异常处理的方式1——捕获异常

异常处理,python中是通过try-except语句代码块来执行处理的

try-except语句代码块处理异常通常有这样几种方式

  1. 使用try-except直接包含并处理所有异常

执行代码如下

try:
n = input("请输入数字:")
num1 = int(n)
print("您输入了数字:" + str(num1))
except:
print("出现异常,处理异常")
print("程序继续执行,代码执行完成!")

>![使用try-except直接包含并处理所有异常](http://upload-images.jianshu.io/upload_images/5988045-aa0fafc390a2a4f5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

2. 使用try-except-except-except嵌套处理指定的多个异常
> ```
def add():
    try:
        n = input("请输入数字:")
        num1 = int(n) # 可能出现异常 ValueError
        print("您输入的数字是:" + num1) # 可能出现异常TypeError
    except ValueError as e: # 处理指定的ValueError异常
        print("输入的数据不是数字")
    except TypeError as e: # 处理指定的TypeError异常
        print("整数不能喝数字拼接")
# 调用执行
add()
try-except-except-except嵌套处理指定的多个异常
  1. 使用try-except-except-else处理异常并执行else代码块

我们通过将可能出现异常的代码包含在try语句块中,如果程序执行正常,就执行后续的代码,可以将后续的代码放在else中执行

# 编写记录用户输入的函数
def add():
    try:
        n = input("请输入数字:")
        num1 = int(n)
    except:
        print("输入的数据不是数字")
    else:
        print("您输入的数字是:" + str(num1))
add()
try-except-except-else处理异常并执行else代码块
  1. 使用try-except-except-finally处理异常并在finally中进行后续处理

某些情况下,程序在操作的过程中,需要使用一定的资源,如打开文件读取或者向文件中写入数据,一旦操作完成,需要关闭和文件的链接释放资源。
此时的流程就是:打开文件->读取/写入数据文件->关闭文件
在读取/写入数据到文件时,可能会出现异常,此时的要求时,不论是否出现异常,最后的关闭文件的操作必须执行

# 操作文件的函数
def readFile():
    try:
        # 打开文件
        f = open("d:/test.txt")
        # 写入内容
        f.write("这是要写到文件中的内容")
    except:
        print("文件读写错误")
    finally:
        # 关闭文件
        f.close()
# 执行函数
readFile()
try-except-except-finally处理异常并在finally中进行后续处理
3.2. 异常处理的方式2——抛出异常

某些情况下,我们捕获到异常信息,如果只是简单的进行处理,对后续的程序可能会造成一定的困扰,举一个简单的操作案例:老板让员工老李去采购一批办公用品

老板boss.py,让员工老李Emp.py,采购一批办公用品
员工老李去采购办公用品,结果出现异常情况,店面关门了;此时老李如果将这个异常自行处理了,就没有结果了。老板那里根本不知道老李发生了什么状况,最终功能没有完成的同时老板boss.py模块也没有得到任何结果。
结果就是~程序出现了BUG,老李遗憾的离职了..

换一种思路
老板boss.py,让员工老李Emp.py,采购一批办公用品
员工老李去采购办公用品,结果出现异常情况,店面关门了;此时老李将异常信息自行简单处理了一下,同时抛出异常信息汇报给老板:店面关门~可以做其他准备了;老板接收到老李抛给自己的异常信息,临时调整计划;最后功能完成了,老李升职了。


这时候我们必须得明确:异常可以捕获进行处理,适当的时候异常也需要抛出给调用者处理

请观察我们之前写过的如下代码:

# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 异常的抛出,首先要捕获到异常,将难以理解的异常
# 转换成比较容易理解的异常抛出给调用者
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 定义一个输入数字的函数
def add():
    try:
        n = input("请输入一个数字:")
        num = int(n)
    except:
        # 如果出现异常,将ValueError异常转换成更加容易理解的异常
        raise ValueError("这里需要一个数字,您输入了非数字字符")

add()
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 转换之前抛出的异常
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
请输入一个数字:a
Traceback (most recent call last):
  File "D:/resp_work/PY_WORK/备课/模块化开发/demo05/demo02.py", line 8, in <module>
    add()
  File "D:/resp_work/PY_WORK/备课/模块化开发/demo05/demo02.py", line 4, in add
    num = int(n)
ValueError: invalid literal for int() with base 10: 'a'
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 转换之后抛出的异常:我们可以看到,这里的异常错误信息非常明确了
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Traceback (most recent call last):
  File "D:/resp_work/PY_WORK/备课/模块化开发/demo05/demo02.py", line 8, in <module>
    add()
  File "D:/resp_work/PY_WORK/备课/模块化开发/demo05/demo02.py", line 6, in add
    raise ValueError("这里需要一个数字,您输入了非数字字符")
ValueError: 这里需要一个数字,您输入了非数字字符

抛出异常有两种情况,第一种情况,当前代码中可能存在异常,如果一旦出现异常直接抛出,让调用者进行后续的处理,第二种情况,当前代码中可能存在异常,但是出现异常的错误提示信息非常不明确,需要转换成我们定义的另一种异常抛出异常,让调用者更加明确出现的问题
不论是异常处理,还是抛出异常,核心都是为了更加方便的解决问题!

3.3. 异常处理的方式3——抛出自定义异常

如果系统提供的异常不一定符合我们的需要,如用户登录失败,需要提示一个账号密码有误的异常信息,python中是没有提供这样的异常对象的,需要开发人员自定义异常来进行处理

我们从前面的内容中已经知道,所有的异常对象都是直接或者间接继承自BaseException
所以自定义异常如下:

# 自定义异常
class MyException(BaseException):
    pass
# 函数处理
def add():
    try:
        n = input("请输入一个数字:")
        num = int(n)
    except ValueError as e:
        # 抛出自定义异常信息
        raise MyError("这里需要一个数字,您输入了非数字字符%s" % n)

add()

自定义异常,在一定程度上扩展了异常的功能,更加方便我们在程序中进行不同错误的不同的处理手段和错误提示信息,使用的时候根据实际需要进行处理即可!

4. 常见的异常

BaseException 所有异常的基类
SystemExit 解释器请求退出
KeyboardInterrupt 用户中断执行(通常是输入^C)
Exception 常规错误的基类
StopIteration 迭代器没有更多的值
GeneratorExit 生成器(generator)发生异常来通知退出
StandardError 所有的内建标准异常的基类
ArithmeticError 所有数值计算错误的基类
FloatingPointError 浮点计算错误

OverflowError 数值运算超出最大限制
ZeroDivisionError 除(或取模)零 (所有数据类型)
AssertionError 断言语句失败
AttributeError 对象没有这个属性
EOFError 没有内建输入,到达EOF 标记
EnvironmentError 操作系统错误的基类
IOError 输入/输出操作失败
OSError 操作系统错误
WindowsError 系统调用失败
ImportError 导入模块/对象失败
LookupError 无效数据查询的基类
IndexError 序列中没有此索引(index)
KeyError 映射中没有这个键
MemoryError 内存溢出错误(对于Python 解释器不是致命的)
NameError 未声明/初始化对象 (没有属性)
UnboundLocalError 访问未初始化的本地变量
ReferenceError 弱引用(Weak reference)试图访问已经垃圾回收了的对象
RuntimeError 一般的运行时错误
NotImplementedError 尚未实现的方法
SyntaxError Python 语法错误
IndentationError 缩进错误
TabError Tab 和空格混用
SystemError 一般的解释器系统错误
TypeError 对类型无效的操作
ValueError 传入无效的参数
UnicodeError Unicode 相关的错误
UnicodeDecodeError Unicode 解码时的错误
UnicodeEncodeError Unicode 编码时错误
UnicodeTranslateError Unicode 转换时错误
Warning 警告的基类
DeprecationWarning 关于被弃用的特征的警告
FutureWarning 关于构造将来语义会有改变的警告
OverflowWarning 旧的关于自动提升为长整型(long)的警告
PendingDeprecationWarning 关于特性将会被废弃的警告
RuntimeWarning 可疑的运行时行为(runtime behavior)的警告
SyntaxWarning 可疑的语法的警告
UserWarning 用户代码生成的警告


大牧莫邪.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • Python异常处理 异常概念: 异常:就是不正常的情况,程序开发过程中错误和BUG都是补充正常的情况 异常发生的...
    youngkun阅读 913评论 0 4
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,566评论 25 707
  • python使用被称为异常的特殊对象来管理程序执行期间发生的错误。每当发生让python不知所措的错误时,...
    路_尧知百战胜阅读 470评论 0 0
  • 边剔着牙,边想:牙,是真的不好了。 女儿曾痛心疾首:一口好好的牙,让自己生生糟蹋坏了。 想想自己的行为...
    莒子阅读 1,196评论 15 4
  • 营救 紫膺文 我来了很久了,依然不知这里是什么地方。周围三三两两地坐着、躺着一些人,他们也不说什么,不做什么,只是...
    紫膺阅读 292评论 0 0