打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
python 异常处理和断言

资料来源: https://book.apeland.cn/details/321/

目录

    • 介绍
    • 异常类型
      • 内置异常类的继承关系
      • 常见异常类型
    • 捕获多个异常
    • 其它异常结构
    • 主动触发异常raise
      • raise是更好的return
      • 单独一句raise的作用
    • 自定义异常
    • 断言

介绍

在编程过程中为了增加友好性,在程序出现bug时一般不会将错误信息显示给用户让用户蒙逼,而是显示一个更友好的提示信息。

语法

try:
    """your code"""
except Exception:
    """上面的程序执行出错后就指行这里的代码"""

需求:将用户输入的两个数字相加

while True:
    num1 = input('num1:')
    num2 = input('num2:')
    try:
        num1 = int(num1)
        num2 = int(num2)
        result = num1 + num2
    except Exception as e:
        print('出现异常,信息如下:')
        print(e)

输入的不是数字的话,执行出错的异常就会把捕捉到

像上面这个Exception异常,几乎能捕捉到所有的错误,这不一定是好事,因为程序出错的原因有很多种,我可能希望出不同的错误就执行不同的异常处理逻辑。 全执行同一逻辑的话会增加程序调试难度,因为你不知道是什么原因导致的错误

异常类型

内置异常类的继承关系

BaseException ------------------------------------- 所有异常的基类
 +-- SystemExit ----------------------------------- 解释器请求退出
 +-- KeyboardInterrupt  --------------------------- 用户中断执行(通常是输入^C)
 +-- GeneratorExit -------------------------------- 生成器(generator)发生异常来通知退出
 +-- Exception  ----------------------------------- 常规错误的基类
      +-- StopIteration --------------------------- 迭代器没有更多的值
      +-- StopAsyncIteration
      +-- ArithmeticError  ------------------------ 所有数值计算错误的基类
      |    +-- FloatingPointError ----------------- 浮点计算错误
      |    +-- OverflowError ---------------------- 数值运算超出最大限制
      |    +-- ZeroDivisionError ------------------ 除(或取模)零 (所有数据类型)
      +-- AssertionError -------------------------- 断言语句失败
      +-- AttributeError -------------------------- 对象没有这个属性
      +-- BufferError
      +-- EOFError -------------------------------- 没有内建输入,到达EOF 标记
      +-- ImportError ----------------------------- 导入模块/对象失败
      |    +-- ModuleNotFoundError
      +-- LookupError ----------------------------- 无效数据查询的基类
      |    +-- IndexError ------------------------- 序列中没有此索引(index)
      |    +-- KeyError --------------------------- 映射中没有这个键
      +-- MemoryError ----------------------------- 内存溢出错误(对于Python 解释器不是致命的)
      +-- NameError ------------------------------- 未声明/初始化对象 (没有属性)
      |    +-- UnboundLocalError ------------------ 访问未初始化的本地变量
      +-- OSError --------------------------------- 操作系统错误
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError -------------------------- 弱引用(Weak reference)试图访问已经垃圾回收了的对象
      +-- RuntimeError ---------------------------- 一般的运行时错误
      |    +-- NotImplementedError ---------------- 尚未实现的方法
      |    +-- RecursionError
      +-- SyntaxError ----------------------------- Python 语法错误
      |    +-- IndentationError ------------------- 缩进错误
      |         +-- TabError ---------------------- Tab 和空格混用
      +-- SystemError ----------------------------- 一般的解释器系统错误
      +-- TypeError ------------------------------- 对类型无效的操作
      +-- ValueError ------------------------------ 传入无效的参数
      |    +-- UnicodeError ----------------------- Unicode 相关的错误
      |         +-- UnicodeDecodeError ------------ Unicode 解码时的错误
      |         +-- UnicodeEncodeError ------------ Unicode 编码时错误
      |         +-- UnicodeTranslateError --------- Unicode 转换时错误
      +-- Warning --------------------------------- 警告的基类
           +-- DeprecationWarning ----------------- 关于被弃用的特征的警告
           +-- PendingDeprecationWarning ---------- 关于特性将会被废弃的警告
           +-- RuntimeWarning --------------------- 可疑的运行时行为(runtime behavior)的警告
           +-- SyntaxWarning ---------------------- 可疑的语法的警告
           +-- UserWarning ------------------------ 用户代码生成的警告
           +-- FutureWarning ---------------------- 关于构造将来语义会有改变的警告
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning

常见异常类型

  • AttributeError 试图访问一个对象没有的属性,比如foo.x,但是foo没有属性x
class A:
    pass

try:
    a = A()
    print(a.name)  # name不存在
except AttributeError as e:
    print(e) # 'A' object has no attribute 'name'
  • IOError 输入/输出异常;基本上是无法打开文件
try:
    f = open("不存在的文件.txt", mode="r", encoding="utf-8")
except IOError as e:
    print(e)  # [Errno 2] No such file or directory: '不存在的文件.txt'
  • ImportError 无法引入模块或包;基本上是路径问题或名称错误
import importlib
try:
    metaclass = importlib.import_module("import_lib.metaclasss")
except ImportError as e:
    print(e)  # No module named 'import_lib.metaclasss'
  • IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5]
try:
    a = [1, 2, 3]
    print(a[9])
except IndexError as e:
    print(e)
  • KeyError 试图访问字典里不存在的键
try:
    a = {"name": "张小小"}
    print(a["age"])
except KeyError as e:
    print(e)	# age
  • KeyboardInterrupt Ctrl+C被按下
"""
命令行程序运行期间,如果用户想终止程序,一般都会采用Ctrl-C快捷键,
这个快捷键会引发python程序抛出KeyboardInterrupt异常。
我们可以捕获这个异常,在用户按下Ctrl-C的时候,进行一些清理工作。
注意,携程except Exception将无法捕获KeyboardInterrupt异常。
"""
try:
    # many code here
except KeyboardInterrupt as e:
    # do something
  • NameError 使用一个还未被赋予对象的变量
try:
    print(aa) # aa未定义
except NameError as e:
    print(e) # name 'aa' is not defined
  • TypeError 传入对象类型与要求的不符合
try:
    print(float(['3']))
except TypeError as e:
    print(e) # float() argument must be a string or a number, not 'list'
  • ValueError 传入一个调用者不期望的值,即使值的类型是正确的
"""
float函数可以接受字符串,即float('5'),只是float('string')中的值'string'是不合适的(不可转换的)字符串
另一方面
因此,如果您尝试float(['5']),就会得到一个TypeError,因为列表永远不能转换为浮点。
"""
try:
    print(float('ooo'))
except ValueError as e:
    print(e) # could not convert string to float: 'ooo'

import math
try:
    print(math.sqrt(-10))
except ValueError as e:
    print(e) # math domain error
  • UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,导致你以为正在访问它
  • IndentationError 语法错误(的子类);代码没有正确对齐
  • SyntaxError Python代码非法,代码不能编译(个人认为这是语法错误,写错了)

捕获多个异常

写程序时需要考虑到try代码块中可能出现的任意异常,可以这样写:

s1 = 'hello'
try:
    int(s1)
except IndexError as e:
    print(e)
except KeyError as e:
    print(e)
except ValueError as e:
    print(e)

如果上面3个异常依然没有匹配到对应的错误 怎么办? 可以在程序最后加上Exception这个万能异常。

一个except还可以同时包含多个异常,即相当于我们对某几个不同的异常,采用同样的处理
except后面跟着的多个异常类型,要把它们写成tuple的形式。

import os


def test_exception2(num):
    try:
        a = 1/num
        if num == 1: os.remove('NotExistedFile')
    except (ZeroDivisionError, FileNotFoundError) as e:
        print('3---', repr(e))


test_exception2(0)
ZeroDivisionError('division by zero')
test_exception2(1)
FileNotFoundError(2, 'No such file or directory')

其它异常结构

try:
    # 主代码块
    pass
except KeyError,e:
    # 异常时,执行该块
    pass
else:
    # 主代码块执行完,若未触发任何异常,执行该块
    pass
finally:
    # 无论监测的代码是否发生异常,都执行该处代码
    pass

主动触发异常raise

try:
    raise Exception('错误了。。。')
except Exception as e:
    print(e)

raise是更好的return

该资料来源:https://www.pynote.net/archives/1175

编写软件,大部分代码都是在处理各种异常和错误。我们常常会遇到这样的场景,代码流程需要一层层的判断底层的返回是否成功,
这样的代码写起来其实很费劲,为了一个可能出现的错误,要在每一个获取返回值的地方写if判断。
其实,这个时候,使用raise来抛出一个异常,比用return返回标志位(True或False),更加简单,代码的可读性和可维护性也更好,代码的层次感也越强。

return语句只能返回到上一层调用的地方,如果调用层次比较多,底层的问题,要层层传递上来就太费劲了,
这样代码写起来看起来都很别扭。return主要还是用来返回数据的,而raise是更好的“返回异常”的方式。

在一个处于层层调用关系的流程中,不管哪个地方raise抛出一个异常,我们只需要在流程需要的地方try…except…捕获异常,
就可以了。raise抛出异常后,代码返回到最近的try…except…的地方(这是个与return很不一样的细节),这样中间流程的代码,
写起来就会很轻松惬意优雅。而且,如果中间虽捕获了异常,但是不对异常进行处理,也可以直接独立的依据raise,再次将异常抛出,交给更上层来处理。

举个例子:

def level_1():
    raise ValueError("this is a value error")


def level_2():
    level_1()
    print("in level 2")


def level_3():
    level_2()
    print("in level 3")


def top():
    level_3()
    print("in top")


try:
    top()
except:
    print("catch exception from level 1")

# 输出:catch exception from level 1

以上示例代码,在最底层的函数raise一个ValueError异常,top函数与直接raise异常的函数,中间还经过了两层调用。
不过,运行程序发现,最底层raise之后,在最顶层直接捕获异常,而且,很重要的细节是,代码中所有的打印都没有执行,
代码相当于从最底层直接return到了最顶层try…except…的地方。

这种代码的写法,比一层层return再判断,要简单很多。这种层层调用在软件中很常见,稍微封装一下底层接口代码,
层次关系就出现了。如果再学会了自定义Python的异常类,配合这种写法,您的代码一定会更加漂亮性感!

单独一句raise的作用

代码中常常能看到单独使用一句raise,后面不带任何参数,这样写的作用是,将向下文当前的异常抛出(raise语句不带参数的默认动作)。

def do_raise():
    raise ValueError('test value error')


def middle():
    try:
        do_raise()
    except:
        print('something wrong')
        raise


def top():
    try:
        middle()
    except ValueError as e:
        print(repr(e))


top()

在middle函数中,单独使用raise语句,它将会被do_raise抛出的异常,直接在此抛出。middle函数不对此异常进行处理,而是交给上层代码去处理。这段代码的运行效果如下:

something wrong
ValueError('test value error')

raise在层层调用的代码流程中,简化了异常处理的代码编写,并形成了自己独有的异常处理层次关系,使得代码在处理异常时非常灵活高效。

自定义异常

class MyException(BaseException): # BaseException是所有异常的基类
    def __init__(self,msg):
        self.message = msg
    def __str__(self):
        return self.message
try:
    raise MyException("我的错误")
except MyException as e:
    print(e)

断言

  • assert语法用于判断代码是否符合执行预期
  • 一般来说在做单元测试的时候用的比较多,在生产环境代码运行的情况下,不建议使用断言,会让程序abort掉。
assert  type(1) is int
assert  1+1 == 2
assert  1+1 == 3 # 会报AssertionError

应用场景举例,别人调你的接口,你的接口要求他调用时必须传递指定的关键参数,等他传递进来时,你就可以用用assert语句他传的参数是否符合你的预期

def my_interface(name,age,score):
    assert type(name) is str
    assert type(age) is int
    assert type(score) is float
my_interface("alex",22,89.2)
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
深入理解Python异常处理:从基础到高级
一道Python面试题:你真的理解finally了吗?
Python3:Collatz 序列(考拉咨猜想)
跟光磊学Python开发
python基础篇:如何把异常处理做的更加优雅
【Python3之异常处理】
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服