python异常处理

  • 异常处理

在程序运行过程中,总会遇到各种各样的问题和错误。有些错误是我们编写代码时自己造成的,比如语法错误、调用错误,甚至逻辑错误。还有一些错误,则是不可预料的错误,但是完全有可能发生的,比如文件不存在、磁盘空间不足、网络堵塞、系统错误等等。这些导致程序在运行过程中出现异常中断和退出的错误,我们统称为异常。大多数的异常都不会被程序处理,而是以错误信息的形式展现出来。

异常有很多种类型,Python内置了几十种常见的异常,就在builtins模块内,无需特别导入,直接就可使用。需要注意的是,所有的异常都是异常类,首字母是大写的!

Python内置了一套try...except...finally(else)...的异常处理机制,来帮助我们进行异常处理。其基本语法是:

try:
    pass
except Exception as ex:        #as 表示取别名
    pass
注:在Python3中,原来的Python2中的except Exception , ex的取别名方法已经不能使用。逗号,被认为是两种异常的分隔符,而不是取别名。

Python的异常机制具有嵌套处理的能力,比如下面的函数f3()调用f2(),f2()调用f1(),虽然是在f1()出错了,但只需要在f3()进行异常捕获,不需要每一层都捕获异常。

def f1():
    return 10/0

def f2():
    f1()

def f3():
    f2()

f3()
>>>
Traceback (most recent call last):
  File "/Users/mlamp/Documents/py_project/parameter.py", line 87, in <module>
    f3()
  File "/Users/mlamp/Documents/py_project/parameter.py", line 86, in f3
    f2()
  File "/Users/mlamp/Documents/py_project/parameter.py", line 83, in f2
    f1()
  File "/Users/mlamp/Documents/py_project/parameter.py", line 81, in f1
    return 10/0
ZeroDivisionError: division by zero

----------------------------------------

try:
    f3()
except ZeroDivisionError as e:        #as e 表示将ZeroDivisionError的详情赋值给 e,然后处理 e
    print(e)

>>>
division by zero

try…except…语句处理异常的工作机制如下

  • 首先,执行try子句(在关键字try和关键字except之间的语句)
  • 如果没有异常发生,忽略except子句,try子句执行后结束。
  • 如果在执行try子句的过程中发生了异常,那么try子句余下的部分将被忽略。如果异常的类型和 except 之后的名称相符,那么对应的except子句将被执行。

如果一个异常没有与任何的except匹配,那么这个异常将会传递给上层的try中。也就是前面说的嵌套处理能力。直到程序最顶端如果还没有被捕获,那么将弹出异常

try:
    try:
        print("发生异常之前的语句正常执行")
        print(1/0)
        print("发生异常之后的语句不会被执行")
    except ValueError as e:
        print(e)

except ZeroDivisionError as e:
    print(e)
    print("里层没有抓好,只能辛苦我外层了")
>>>
发生异常之前的语句正常执行
division by zero
里层没有抓好,只能辛苦我外层了

可能包含多个except子句,分别来处理不同的特定的异常。但最多只有一个分支会被执行。所以except子句有排序先后问题,进了一条巷子就不会进别的巷子。

try:
    print("发生异常之前的语句正常执行")
    print(1/0)
    print("发生异常之后的语句不会被执行")
except NameError as e:
    print(e)
except ZeroDivisionError as e:
    print("我是第一个抓取到除零异常的")
except (ValueError,ZeroDivisionError) as e:
    print("我是备胎")

>>>
发生异常之前的语句正常执行
我是第一个抓取到除零异常的

通用异常 Exception

在Python的异常中,有一个通用异常:Exception,它可以捕获任意异常

s1 = 'hello'
int(s1)
>>>
ValueError: invalid literal for int() with base 10: 'hello'
------------------------
try:
    int(s1)
except Exception as e:    #Exception异常会将所有的异常的详情赋值给e,然后处理 e
    print(e)

>>>
invalid literal for int() with base 10: 'hello'

那么既然有这个什么都能管的异常,其他诸如OSError、ValueError的异常是不是就可以不需要了?当然不是!很多时候程序只会弹出那么几个异常,没有必要针对所有的异常进行捕获,那样的效率会很低。另外,根据不同的异常种类,制定不同的处理措施,用于准确判断错误类型,存储错误日志,都是非常有必要甚至强制的。

finally和else子句

try except语法还有一个可选的else子句,如果使用这个子句,那么必须放在所有的except子句之后。这个子句将在try子句没有发生任何异常的时候执行。当try子句发生异常以后,就不会执行else后面的子句

同样的,还有一个可选的finally子句。无论try执行情况和except异常触发情况,finally子句都会被执行!

try:
    print('try...')
    r = 10 / int('a')
    print('result:', r)
except ValueError as e:
    print('ValueError:', e)
finally:
    print('finally...')
print('END')
>>>
try...
ValueError: invalid literal for int() with base 10: 'a'
finally...
END

当else和finally同时存在时:

try:                #try后面没有异常发生
    pass        
except:
    pass
else:
    print("else")
finally:
    print("finally")
>>>
else
finally
------------------------------------------
try:            #try 后面有异常发生
    1/0
except:
    pass
else:
    print("else")
finally:
    print("finally")

>>>
finally

主动抛出异常:raise

很多时候,我们需要主动抛出一个异常。Python内置了一个关键字raise,可以主动触发异常

>>> raise
Traceback (most recent call last):
  File "<pyshell#0>", line 1, in <module>
    raise
RuntimeError: No active exception to reraise
>>> raise NameError("kkk")
Traceback (most recent call last):
  File "<pyshell#1>", line 1, in <module>
    raise NameError("kkk")
NameError: kkk

raise唯一的一个参数指定了要被抛出的异常的实例,如果什么参数都不给,那么会默认抛出当前异常

try:
    1/0
except ZeroDivisionError as ex:
    print("记录异常日志: ", ex)
    print("但是我自己无法处理,只能继续抛出,看看上层能否处理(甩锅)")
    raise

有时候,你需要主动弹出异常,作为警告或特殊处理:

sex = int(input("Please input a number: "))

try:
    if sex == 1:
        print("这是个男人!")
    elif sex == 0:
        print("这是个女人!")
    else:
        print("好像有什么不符合常理的事情发生了!!")
        raise ValueError("非法的输入")
except ValueError:
    print("性别不明确!!!")

更多的时候,你需要使用raise抛出你自定义的异常

自定义异常

Python内置了很多的异常类,并且这些类都是从BaseException类派生的。

下面是一些常见异常类,请把它们记下来!这样你在见到大多数异常的时候都能快速准确的判断异常类型。

异常名解释
AttributeError试图访问一个对象没有的属性
IOError输入/输出异常
ImportError无法引入模块或包;多是路径问题或名称错误
IndentationError缩进错误
IndexError下标索引错误
KeyError试图访问不存在的键
KeyboardInterruptCtrl+C被按下,键盘终止输入
NameError使用未定义的变量
SyntaxError语法错误
TypeError传入对象的类型与要求的不符合
UnboundLocalError试图访问一个还未被设置的局部变量
ValueError传入一个调用者不期望的值,即使值的类型是正确的
OSError操作系统执行错误

大多数情况下,上面的内置异常已经够用了,但是有时候你还是需要自定义一些异常。自定义异常应该继承Exception类,直接继承或者间接继承都可以,例如:

class MyExcept(Exception):

    def __init__(self, msg):
        self.message = msg

try:
    raise MyExcept('我的异常!')
except MyExcept as ex:
    print(ex)
>>>
我的异常
  • python 代码调试

调试

有的问题简单,有的问题很复杂,但不管简单还是复杂,通常我们都无法一眼就能看出问题的具体性质。我们需要知道出错时,哪些变量的值是正确的,哪些变量的值是错误的,某些关键的代码是否符合我们的预期,这些就需要我们将过程状态输出到屏幕或日志上,方便我们检查和分析。

1. 初学者简单粗暴的方法:print大法

print()方法是新手最常用的方法,也是比较简单的方法。但是,用print()最大的问题是将来还得删掉它,而且代码里多了很多无用的行,想想程序里到处都是print(),运行结果也会包含很多垃圾信息,还影响执行效率,代码一点也不简洁优雅,就觉得不是那么愉快了。

2. 断言assert

Python内置了一个assert关键字,表示断言。凡是用print()来辅助查看的地方,都可以用断言来替代。它会对后面的表达式进行判断,如果表达式为True,那就什么都不做,程序接着往下走;如果False,那么就会弹出异常

assert是一个非常有用的技巧,通过选择关键因子,对因子的状态进行判定,可以将程序一块一块的进行划分,逐步的缩小问题范围。但是,程序中如果到处充斥着assert,和print()相比也好不到哪去。不过,启动Python解释器时可以用-O参数来关闭assert。关闭后,所有的assert语句相当于pass语句。

3. 日志logging

logging是Python内置的一个日志模块,不但可以将信息在屏幕上打印,还可以输出到文件,保留下来。使用logging之前,需要先通过import logging导入该模块

logging允许你指定记录信息的级别,有DEBUG,INFO,WARNING,ERROR等几种级别,级别参数level会忽略比它低的级别信息。当指定level=INFO时,logging.DEBUG就不起作用了。同理,指定level=WARNING后,DEBUGINFO就不起作用了。logging.basicConfig(level=logging.INFO)这行就是指定只有INFO以上级别的信息才会被记录下来。

4. Pycharm调试方法

单元测试之unittest

单元测试是用来对一个模块、一个函数或者一个类进行正确性检验的工作。你的代码可能在语法、词法和运行过程中没有问题了,但是并不能代表它就完全符合你的设计预期,很有可能你希望得到A,它给你的结果却是B。这就需要我们对程序进行单元测试。单元测试测的不是语法问题,而是业务逻辑是否正确的问题

一旦编写好单元测试,我们就可以运行单元测试。最简单的运行方式是在代码的最后加上:

if __name__ == '__main__':
    unittest.main()

另一种方法是在命令行中通过参数-m unittest运行单元测试:

$ python -m unittest 测试脚本
  • python 模块与包

在编程语言中,代码块、函数、类、模块,一直到包,逐级封装,层层调用。在Python中,一个.py文件就是一个模块,模块是比类更高一级的封装。在其他语言,被导入的模块也通常称为库。

模块可以分为自定义模块、内置模块和第三方模块

为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package),包是模块的集合,比模块又高一级的封装。没有比包更高级别的封装,但是包可以嵌套包,就像文件目录一样,

包名通常为全部小写,避免使用下划线

在Python中,模块(包、类、函数)的导入方式有以下四种:

  • import xx.xx
  • from xx.xx import xx
  • from xx.xx import xx as rename    为了避免命名冲突,在导入的时候,可以给导入的对象重命名
  • from xx.xx import *         将对象内的所有内容全部导入。非常容易发生命名冲突,请慎用!

默认情况下,模块的搜索顺序是这样的:

  1. 当前执行脚本所在目录
  2. Python的安装目录
  3. Python安装目录里的site-packages目录

其实就是“自定义”——>“内置”——>“第三方”模块的查找顺序。任何一步查找到了,就会忽略后面的路径,所以模块的放置位置是有区别的。

一般在pycharm中导入时出错,可以在命令行中执行,就可以找到错误的原因

包(Package):

只有包含__init__.py文件的目录才会被认作是一个包

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值