精华内容
下载资源
问答
  • 在本篇文章里
  • 需要的题量多的话建议购买付费专栏(包含上百道题目答案,并持续更新中),性价比更高。
  • Python 异常处理的实例详解 与许多面向对象语言一样,Python 具有异常处理,通过使用 try…except 块来实现。 Note: Python v s. Java 的异常处理 Python 使用 try…except 来处理异常,使用 raise 来引发异常。...
  • 需要的题量多的话建议购买付费专栏(包含上百道题目答案,并持续更新中),性价比更高。
  • python 异常处理总结

    2020-12-23 13:23:15
    1.Python异常类    异常 描述 NameError 尝试访问一个没有申明的变量 ZeroDivisionError 除数为0 SyntaxError 语法错误 IndexError 索引超出序列范围 KeyError 请求一个不存在的字典关键字 IOError...
  • 简单介绍Python中的错误和异常,重点介绍Python中的异常处理机制,包括主要的关键字:try、except、finally、else等,以及它们组合使用时程序执行的流程等,非常适合高校老师教学和学生课后复习使用。
  • 主要介绍了通过实例了解Python异常处理机制底层实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 在本篇文章中小编给大家分享了关于Python异常处理的相关知识点以及对应的实例内容,需要的朋友们学习下。
  • 通通过过实实例例了了解解Python异异常常处处理理机机制制底底层层实实现现 这篇文章主要介绍了通过实例了解Python异常处理机制底层实现,文中通过示例代码介绍的非常详细对大家的学 习或者工作具有 定的参考学习价值...
  • Python异常处理

    万次阅读 多人点赞 2019-04-20 21:31:10
    异常处理结构中最常见也最基本的结构。其中try子句中的代码块包含可能出现的语句,而except子句用来不做相应的异常,except子句中的代码块用来处理异常。如果try中的代码块没有出现异常,则继续往下执行异常处理结构...

    1. try…except结构
            异常处理结构中最常见也最基本的结构。其中try子句中的代码块包含可能出现的语句,而except子句用来不做相应的异常,except子句中的代码块用来处理异常。如果try中的代码块没有出现异常,则继续往下执行异常处理结构后面的代码;如果出现异常并且被except子句捕获,则执行except子句中的异常处理代码;如果出现异常单没有被except捕获,则继续往外层抛出;如果所有层都没有捕获并处理该异常,则程序终止并将该异常抛给最终用户。语法结构如下:
    try:
            try块
    except Exception[as reason]:
            exception块
            如果要捕获所有类型异常,可以使用BaseException,即Python异常类的基类,代码格式如下:
    try:
            …
    except BaseException as e:
            exception块
    2. try …except…else结构
            带else子句的异常处理结构是一种特殊形式的选择结构。如果try中的代码抛出了异常,并且被某个except捕获,则执行相应的异常处理代码,这种情况下不会执行else中的代码,依赖于try代码块成功执行的代码都应该放到else代码块中;如果try中的代码没有抛出任何异常,则执行else块中的代码。
            工作原理:Python尝试执行try代码块中的代码;只有可能引发异常的代码才需要放在try语句中。有时候,有一些仅在try代码块成功执行时才需要运行的代码,这些代码应放在else代码块中。except代码块告诉Python,如果他尝试运行try代码块中的代码时引发了指定的异常,该怎么办。通过预测可能发生错误的代码,可编写健壮的程序,它们即使面临无效数据或缺少资源,也能继续运行,从而能抵御无意的影虎错误和恶意的攻击。
    3. 带有多个except的try结构
            在实际开发中,同一段代码可能会抛出多个异常,需要针对不同异常类型进行相应的处理。为了支持多个异常的捕捉和处理,Python提供了带有过个except的异常处理结构,类似于多分支选择结构。一旦某个except捕获了异常,则后面剩余的except子句将不会再执行。
            将要捕获的异常卸载一个元组中,可以使用一个except语句捕获多个异常,并且共用同一段异常处理代码,当然,除非确定要捕获的多个异常可以使用同一段代码来处理,否则并不建议这样做。
    4. try…except…finally结构
            该结构指那个,finally自剧中的语句块无论是否发生异常都会执行,常用来做一些清理工作以释放try自剧中申请的资源。
            需要注意的问题是,如果try子句中的异常没有被捕获和处理,或者except子句或else子句中的代码出现了异常,那么这些异常将会在finally子句执行完成后再次抛出。finally中的代码也可能会抛出异常,使用带有finally子句的异常处理结构时,应尽量避免在子句中使用return语句,否则可能会出现出乎意料的错误。
    5. 断言
            Python在unittest.TestCase类中提过了很多断言的方法。断言方法检查你认为该满足的条件是否确实满足。如果不满足Python将引发异常。
    语法:
            assert expression[,reason]
            assert语句一般用于对程序某个时刻必须满足的条件进行验证,仅当”debug” 为True时有效。当PYthon脚本以_()选项编译为字节码文件是,assert语句将被移除以提高运行速度。
    6. 上下文管理
            使用上下文管理语句with可以自动管理资源,在代码块执行完毕后自动还原进入改代码块之前的现场或上下文。不论何种原因跳出with块,也不论是否发生异常,总能保证资源被正确释放,大大简化了程序员的工作,常用于文件操作、网络通信之类的场合。
            with语句的语法如下:
            with context_expr [as var]:
                    with块
    7. 用sys模块回溯最后的异常
            当发生异常时Python会回溯异常,给出大量的提示,可能会给程序员的定位和纠错带来一定的困难,这是可以使用sys模块回溯最近一次异常。语法为:
    import sys
    try:
            block
    except:
            t==sys.exc_info()
            print(t)
            sys.exc_info()返回值是一个三元组(type,value/message,traceback)。其中,type表示异常的类型,value/message表示异常的信息或者参数,而traceback则包含调用栈信息的对象。
            sys.exc_info()可以直接定位最终引发异常的原因,结果比较简洁,但是缺点是难以直接确定引发异常的代码位置

    展开全文
  • Python异常处理总结

    2021-01-20 04:46:45
    本文较为详细的罗列了Python常见的异常处理,供大家参考,具体如下: 1. 抛出异常和自定义异常 Python用异常对象(exception object)表示异常情况,遇到错误后,会引发异常。如果异常对象并未被处理或捕捉,程序就会...
  • Python异常处理(基础详解)

    千次阅读 多人点赞 2020-04-06 13:52:05
    本文是python异常的基础知识,欢迎阅读,一起进步 Python专栏请参考:人生苦短-我学python 文章目录一.异常简介二.捕获异常三.异常的传递四.抛出自定义的异常五.模块六.模块制作七.python中的包 一.异常简介 ...

    本文是python异常的基础知识,欢迎阅读,一起进步



    一.异常简介

    在这里插入图片描述

    • 异常是指在语法正确的前提下,程序运行时报错就是异常。
    • 当Python脚本发生异常时我们需要捕获处理它,否则程序会终止执行。
    print ('-----test--1---')
    open('123.txt', 'r')
    print ('-----test--2---')
    
    -----test--1---
    Traceback (most recent call last):
      File "D:/Phython/study/venv/Include/hello.py", line 2, in <module>
        open('123.txt', 'r')
    FileNotFoundError: [Errno 2] No such file or directory: '123.txt'
    
    • Why?-->打开一个不存在的文件123.txt,当找不到123.txt 文件时,就会抛出给我们一个IOError类型的错误,No such file or directory:123.txt (没有123.txt这样的文件或目录)

    二.捕获异常

    • 捕获异常 try…except…
    try:
        print('-----test--1---')
        open('123.txt','r')
        print('-----test--2---')
    except IOError:
        pass
    
    -----test--1---
    
    • 说明:
    • 此程序看不到任何错误,因为用except 捕获到了IOError异常,并添加了处理的方法
    • pass 表示实现了相应的实现,但什么也不做;如果把pass改为print语句,那么就会输出其他信息
      在这里插入图片描述
    • 把可能出现问题的代码,放在try中
    • 把处理异常的代码,放在except中

    • except捕获多个异常
    try:
        print (num)
    except IOError:
        print('产生错误了')
    
    Traceback (most recent call last):
      File "D:/Phython/study/venv/Include/hello.py", line 2, in <module>
        print (num)
    NameError: name 'num' is not defined
    

    上例程序,已经使用except来捕获异常了,为什么还会看到错误的信息提示?

    • except捕获的错误类型是IOError,而此时程序产生的异常为 NameError ,所以except没有生效
    • 正确写法
    try:
        print num
    except NameError:
        print('产生错误了')
    
    产生错误了
    

    • 实际开发中,捕获多个异常的方式,如下:
    #coding=utf-8
    try:
        print('-----test--1---')
        open('123.txt','r') # 如果123.txt文件不存在,那么会产生 IOError 异常
        print('-----test--2---')
        print(num)# 如果num变量没有定义,那么会产生 NameError 异常
    
    except (IOError,NameError): 
        #如果想通过一次except捕获到多个异常可以用一个元组的方式
        print("捕捉到异常")
    
    -----test--1---
    捕捉到异常
    
    • 换个顺序对比一下
    #coding=utf-8
    try:
        print(num)# 如果num变量没有定义,那么会产生 NameError 异常
        print('-----test--1---')
        open('123.txt','r') # 如果123.txt文件不存在,那么会产生 IOError 异常
        print('-----test--2---')
    
    
    except (IOError,NameError): 
        #如果想通过一次except捕获到多个异常可以用一个元组的方式
        print("捕捉到异常")
    
    捕捉到异常
    
    • 注意: 当捕获多个异常时,可以把要捕获的异常的名字,放到except 后,并使用元组的方式仅进行存储

    • 获取异常的信息描述

    在这里插入图片描述

    在这里插入图片描述


    • 捕获所有异常
      在这里插入图片描述
      在这里插入图片描述

    • else
    • 咱们应该对else并不陌生,在if中,它的作用是当条件不满足时执行的实行;同样在try…except…中也是如此,即如果没有捕获到异常,那么就执行else中的事情
    try:
        num = 100
        print(num)
    except NameError as errorMsg:
        print('产生错误了:%s'%errorMsg)
    else:
        print('没有捕获到异常,真高兴')
    
    100
    没有捕获到异常,真高兴
    

    • try…finally…
    • 在程序中,如果一个段代码必须要执行,即无论异常是否产生都要执行,那么此时就需要使用finally。比如文件关闭,释放锁,把数据库连接返还给连接池等。
    import time
    try:
        f = open('test.txt')
        try:
            while True:
                content = f.readline()
                if len(content) == 0:
                    break
                time.sleep(2)
                print(content)
        except:
            #如果在读取文件的过程中,产生了异常,那么就会捕获到
            #比如 按下了 ctrl+c
            print("捕捉到异常")
        finally:
            f.close()
            print('关闭文件')
    except:
        print("没有这个文件")
    
    没有这个文件
    
    • test.txt文件中每一行数据打印,但是我有意在每打印一行之前用time.sleep方法暂停2秒钟。这样做的原因是让程序运行得慢一些。在程序运行的时候,按Ctrl+c中断(取消)程序。
    • 我们可以观察到KeyboardInterrupt异常被触发,程序退出。但是在程序退出之前,finally从句仍然被执行,把文件关闭。

    三.异常的传递

    • try嵌套中
    import time
    try:
        f = open('test.txt')
        try:
            while True:
                content = f.readline()
                if len(content) == 0:
                    break
                time.sleep(2)
                print(content)
        finally:
            f.close()
            print('关闭文件')
    except:
        print("没有这个文件")
    

    • 函数嵌套调用中
    def test1():
        print("----test1-1----")
        print(num)
        print("----test1-2----")
    
    
    def test2():
        print("----test2-1----")
        test1()
        print("----test2-2----")
    
    
    def test3():
        try:
            print("----test3-1----")
            test1()
            print("----test3-2----")
        except Exception as result:
            print("捕获到了异常,信息是:%s" % result)
    
        print("----test3-2----")
    
    
    test3()
    print("------华丽的分割线-----")
    test2()
    
    ----test3-1----
    ----test1-1----
    捕获到了异常,信息是:name 'num' is not defined
    ----test3-2----
    ------华丽的分割线-----
    ----test2-1----
    ----test1-1----
    Traceback (most recent call last):
      File "D:/Phython/study/venv/Include/hello.py", line 26, in <module>
        test2()
      File "D:/Phython/study/venv/Include/hello.py", line 9, in test2
        test1()
      File "D:/Phython/study/venv/Include/hello.py", line 3, in test1
        print(num)
    NameError: name 'num' is not defined
    
    • 总结:
    • ① 如果try嵌套,那么如果里面的try没有捕获到这个异常,那么外面的try会接收到这个异常,然后进行处理,如果外边的try依然没有捕获到,那么再进行传递。
    • ②如果一个异常是在一个函数中产生的,例如函数A---->函数B---->函数C,而异常是在函数C中产生的,那么如果函数C中没有对这个异常进行处理,那么这个异常会传递到函数B中,如果函数B有异常处理那么就会按照函数B的处理方式进行执行;如果函数B也没有异常处理,那么这个异常会继续传递,以此类推。。。如果所有的函数都没有处理,那么此时就会进行异常的默认处理,即通常见到的那样。
    • ③注意观察上图中,当调用test3函数时,在test1函数内部产生了异常,此异常被传递到test3函数中完成了异常处理,而当异常处理完后,并没有返回到函数test1中进行执行,而是在函数test3中继续执行

    四.抛出自定义的异常

    • 你可以用raise语句来引发一个异常。异常/错误对象必须有一个名字,且它们应是Error或Exception类的子类
    • 下面是一个引发异常的例子:
    class ShortInputException(Exception):
        '''自定义的异常类'''
        def __init__(self, length, atleast):
            #super().__init__()
            self.length = length
            self.atleast = atleast
    
    def main():
        try:
            s = input('请输入 --> ')
            if len(s) < 3:
                # raise引发一个你定义的异常
                raise ShortInputException(len(s), 3)
        except ShortInputException as result:#x这个变量被绑定到了错误的实例
            print('ShortInputException: 输入的长度是 %d,长度至少应是 %d'% (result.length, result.atleast))
        else:
            print('没有异常发生.')
    
    main()
    
    • 情况1
    请输入 --> hello
    没有异常发生.
    
    • 情况2
    请输入 --> la
    ShortInputException: 输入的长度是 2,长度至少应是 3
    
    • 注意
    • 以上程序中,关于代码#super().init()的说明
    • 这一行代码,可以调用也可以不调用,建议调用,因为__init__方法往往是用来对创建完的对象进行初始化工作,如果在子类中重写了父类的__init__方法,即意味着父类中的很多初始化工作没有做,这样就不保证程序的稳定了,所以在以后的开发中,如果重写了父类的__init__方法,最好是先调用父类的这个方法,然后再添加自己的功能

    五.模块

    • Python中的模块有过C语言编程经验的朋友都知道在C语言中如果要引用sqrt函数,必须用语句#include <math.h>引入math.h这个头文件,否则是无法正常进行调用的。

    那么在Python中,如果要引用一些其他的函数,该怎么处理呢?

    • 在Python中有一个概念叫做模块(module),这个和C语言中的头文件以及Java中的包很类似,比如在Python中要调用sqrt函数,必须用import关键字引入math这个模块,下面就来了解一下Python中的模块。
    • 说的通俗点模块就好比是工具包,要想使用这个工具包中的工具(就好比函数),就需要导入这个模块

    • import
    • 在Python中用关键字import来引入某个模块,比如要引用模块math,就可以在文件最开始的地方用import math来引入。
    import module1,mudule2...
    
    • 当解释器遇到import语句,如果模块在当前的搜索路径就会被导入。
    • 在调用math模块中的函数时,必须这样引用:
      模块名.函数名
    

    什么必须加上模块名调用呢 ?

    • 因为可能存在这样一种情况:在多个模块中含有相同名称的函数,此时如果只是通过函数名来调用,解释器无法知道到底要调用哪个函数。所以如果像上述这样引入模块的时候,调用函数必须加上模块名。
    import math
    
        #这样会报错
    print sqrt(2)
    
        #这样才能正确输出结果
    print math.sqrt(2)
    
    • 有时候我们只需要用到模块中的某个函数,只需要引入该函数即可,此时可以用下面方法实现:
    from 模块名 import 函数名1,函数名2....
    
    • ①通过这种方式引入的时候,调用函数时只能给出函数名,不能给出模块名,但是当两个模块中含有相同名称函数的时候,后面一次引入会覆盖前一次引入。也就是说假如模块A中有函数function(),在模块B中也有函数function(),如果引入A中的function在先、B中的function在后,那么当调用function函数的时候,是去执行模块B中的function函数。
    • ②如果想一次性引入math中所有的东西,还可以通过from math import *来实现

    • from…import
    • Python的from语句让你从模块中导入一个指定的部分到当前命名空间中
    • 语法如下:
    from modname import name1[, name2[, ... nameN]]
    
    • 例如,要导入模块fib的fibonacci函数,使用如下语句:
    from fib import fibonacci
    
    • 注意:不会把整个fib模块导入到当前的命名空间中,它只会将fib里的fibonacci单个引入

    • from … import *
    • 把一个模块的所有内容全都导入到当前的命名空间也是可行的,只需使用如下声明:
    from modname import *
    
    • 注意:这提供了一个简单的方法来导入一个模块中的所有项目。然而这种声明不该被过多地使用。

    • as
    import time as tt
    time.sleep(1)
    
    Traceback (most recent call last):
      File "D:/Phython/study/venv/Include/hello.py", line 2, in <module>
        time.sleep(1)
    NameError: name 'time' is not defined
    

    • 定位模块
    • 当你导入一个模块,Python解析器对模块位置的搜索顺序是:
    • ①当前目录
    • ②如果不在当前目录,Python则搜索在shell变量PYTHONPATH下的每个目录。
    • ③如果都找不到,Python会察看默认路径。UNIX下,默认路径一般为/usr/local/lib/python/
    • ④模块搜索路径存储在system模块的sys.path变量中。变量里包含当前目录,PYTHONPATH和由安装过程决定的默认目录。

    六.模块制作

    • 定义自己的模块
    • 在Python中,每个Python文件都可以作为一个模块,模块的名字就是文件的名字。
      比如有这样一个文件test.py,在test.py中定义了函数add
    • test.py
    def add(a, b):
        return a + b
    
    • 调用自己定义的模块
    • 那么在其他文件中就可以先import test,然后通过test.add(a,b)来调用了,当然也可以通过from test import add来引入
    • main.py
    import test
    
    result = test.add(11, 22)
    print(result)
    
    • test.py
    def add(a, b):
        return a + b
    
    
    # 用来进行测试
    ret = add(12, 22)
    print('int test.py file,,,,12+22=%d' % ret)
    
    int test.py file,,,,12+22=34
    
    • 如果此时,在其他py文件中引入了此文件的话,想想看,测试的那段代码是否也会执行呢!
    import test
    
    result = test.add(11, 22)
    print(result)
    
    int test.py file,,,,12+22=34
    33
    

    • 模块中的__all__
    • 没有__all__
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
    • 模块中有__all__
      在这里插入图片描述
      在这里插入图片描述
    • 总结: 如果一个文件中有__all__变量,那么也就意味着这个变量中的元素,不会被from xxx import *时导入

    七.python中的包

    • (1)引入包
    • 有2个模块功能有些联系

    在这里插入图片描述

    • 所以将其放到同一个文件夹下

    在这里插入图片描述

    • 使用import 文件.模块 的方式导入

    在这里插入图片描述

    • 使用from 文件夹 import 模块 的方式导入

    在这里插入图片描述

    • 在msg文件夹下创建__init__.py文件

    在这里插入图片描述

    • 在__init__.py文件中写入

    在这里插入图片描述

    • 重新使用from 文件夹 import 模块 的方式导入

    在这里插入图片描述

    • 总结: 包将有联系的模块组织在一起,即放到同一个文件夹下,并且在这个文件夹创建一个名字为__init__.py 文件,那么这个文件夹就称之为包,有效避免模块名称冲突问题,让应用组织结构更加清晰

    • (2)init.py文件有什么用
    __init__.py 控制着包的导入行为
    
    • init.py为空
    • 仅仅是把这个包导入,不会导入包中的模块
    • __ all__
    • 在__init__.py文件中,定义一个__all__变量,它控制着 from 包名 import *时导入的模块
    • 可以在__init__.py文件中编写内容
    • 可以在这个文件中编写语句,当导入时,这些语句就会被执行

    在这里插入图片描述
    在这里插入图片描述


    • 最后小结几个常见异常
    异常解释
    AttributeError当你访问一个对象的属性,但是这个属性并没有在这个对象定义的时候,就会引发 AttributeError
    ImportError在使用 import 导入模块时,如果要导入的模块找不到,或者从模块中导入模块中不存在的内容
    IndexError当你尝试从序列(如列表或元组)中检索索引,但是序列中找不到该索引。此时就会引发 IndexError
    KeyError与 IndexError 类似,当你访问映射(通常是 dict )中不包含的键时,就会引发 KeyError。
    NameError当你引用了变量、模块、类、函数或代码中没有定义的其他名称时,将引发 NameError。
    SyntaxError当代码中有不正确的 Python 语法时,就会引发 SyntaxError。下面的问题是函数定义行末尾缺少一个冒号
    TypeError当你的代码试图对一个无法执行此操作的对象执行某些操作时,例如将字符串添加到整数中,以及一开始的例子使用 append 方法给元组添加元素,这些都会引发 TypeError。
    ValueError当对象的值不正确时就会引发 ValueError。这个和我们前面说的因为索引的值不在序列的范围内,而导致 IndexError 异常类似。

    • The best investment is in yourself
      在这里插入图片描述
    • 2020.04.06 记录辰兮的第49篇博客
    展开全文
  • 本文实例讲述了Python异常处理操作。分享给大家供大家参考,具体如下: 常见异常 在python中不同的异常可以用不同的类型(python中统一了类与类型,类型即类)去标识,不同的类对象标识不同的异常,一个异常标识一种...
  • 捕获异常 # 对数字变量使用append操作 a = 123 a.apppend(4) 执行这个程序时,会抛出: AttributeError: 'int' object has no attribute 'apppend' 我们使用try:except语句进行捕获。 # 捕获异常 a = 123 try: ...
  • 主要给大家介绍了关于Python异常处理的哲学,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 本文实例讲述了python异常处理、自定义异常、断言原理与用法。分享给大家供大家参考,具体如下: 什么是异常: 当程序遭遇某些非正常问题的时候就会抛出异常:比如int()只能处理能转化成int的对象,如果传入一个不...
  • 本篇文章主要介绍了详解Python异常处理中的Finally else的功能,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 二、异常处理捕捉异常可以使用try/except语句。try/except语句用来检测try语句块中的错误,从而让except语句捕获异常信息并处理。如果你不想在异常发生时结束你的程序,只需在try里捕获它。 异常语法:以下为简单的...
  • Python标准异常总结 小甲鱼总结的Python标准异常类型 检测异常处理异常 try–except语句 try–finally语句 try语句检测到异常,try中剩余的语句都不会执行 try: 检测范围 except Exception[as reason]: 出现异常...
  • 今天小编就为大家分享一篇python异常处理和日志处理方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
  • 主要介绍了python异常处理try except过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • Python异常处理的3个技巧

    千次阅读 2019-09-05 13:55:25
    如果你用 Python 编程,那么你就无法避开异常,因为异常在这门语言里无处不在。打个比方,当你在脚本执行时按 ctrl+c ...异常处理工作由“捕获”和“抛出”两部分组成。“捕获”指的是使用 try...except 包裹特定语...

    如果你用 Python 编程,那么你就无法避开异常,因为异常在这门语言里无处不在。打个比方,当你在脚本执行时按 ctrl+c 退出,解释器就会产生一个 KeyboardInterrupt 异常。而 KeyErrorValueErrorTypeError 等更是日常编程里随处可见的老朋友。

    异常处理工作由“捕获”和“抛出”两部分组成。“捕获”指的是使用 try...except 包裹特定语句,妥当的完成错误流程处理;而恰当的使用 raise 主动“抛出”异常,更是优雅代码里必不可少的组成部分。

    在这篇文章里,我会分享与异常处理相关的 3 个好习惯。继续阅读前,我希望你已经了解了下面这些知识点:

    • 异常的基本语法与用法(建议阅读官方文档 “Errors and Exceptions”)

    • 为什么要使用异常代替错误返回(建议阅读《让函数返回结果的技巧》

    • 为什么在写 Python 时鼓励使用异常 (建议阅读 “Write Cleaner Python: Use Exceptions”)

    三个好习惯

    1. 只做最精确的异常捕获

    假如你不够了解异常机制,就难免会对它有一种天然恐惧感。你可能会觉得:异常是一种不好的东西,好的程序就应该捕获所有的异常,让一切都平平稳稳的运行。而抱着这种想法写出的代码,里面通常会出现大段含糊的异常捕获逻辑。

    让我们用一段可执行脚本作为样例:

    脚本里的 save_website_title 函数做了好几件事情。它首先通过网络获取网页内容,然后利用正则匹配出标题,最后将标题写在本地文件里。而这里有两个步骤很容易出错:网络请求 与 本地文件操作。所以在代码里,我们用一个大大的 try...except 语句块,将这几个步骤都包裹了起来。安全第一 。

    那么,这段看上去简洁易懂的代码,里面藏着什么问题呢?

    如果你旁边刚好有一台安装了 Python 的电脑,那么你可以试着跑一遍上面的脚本。你会发现,上面的代码是不能成功执行的。而且你还会发现,无论你如何修改网址和目标文件的值,程序仍然会报错 “save failed: unable to...”。为什么呢?

    问题就藏在这个硕大无比的 try...except 语句块里。假如你把眼睛贴近屏幕,非常仔细的检查这段代码。你会发现在编写函数时,我犯了一个小错误,我把获取正则匹配串的方法错打成了 obj.grop(1),少了一个'u'( obj.group(1))。

    但正是因为那个过于庞大、含糊的异常捕获,这个由打错方法名导致的原本该被抛出的 AttibuteError 却被吞噬了。从而给我们的 debug 过程增加了不必要的麻烦。

    异常捕获的目的,不是去捕获尽可能多的异常。假如我们从一开始就坚持:只做最精准的异常捕获。那么这样的问题就根本不会发生,精准捕获包括:

    • 永远只捕获那些可能会抛出异常的语句块

    • 尽量只捕获精确的异常类型,而不是模糊的 Exception

    依照这个原则,我们的样例应该被改成这样:

     

    2. 别让异常破坏抽象一致性

    大约四五年前,当时的我正在开发某移动应用的后端 API 项目。如果你也有过开发后端 API 的经验,那么你一定知道,这样的系统都需要制定一套 “API 错误码规范”,来为客户端处理调用错误时提供方便。

    一个错误码返回大概长这个样子:

    在制定好错误码规范后,接下来的任务就是如何实现它。当时的项目使用了 Django 框架,而 Django 的错误页面正是使用了异常机制实现的。打个比方,如果你想让一个请求返回 404 状态码,那么只要在该请求处理过程中执行 raiseHttp404 即可。

    所以,我们很自然的从 Django 获得了灵感。首先,我们在项目内定义了错误码异常类: APIErrorCode。然后依据 “错误码规范”,写了很多继承该类的错误码。当需要返回错误信息给用户时,只需要做一次 raise 就能搞定。

    毫无意外,所有人都很喜欢用这种方式来返回错误码。因为它用起来非常方便,无论调用栈多深,只要你想给用户返回错误码,调用 raiseerror_codes.ANY_THING 就好。

    随着时间推移,项目也变得越来越庞大,抛出 APIErrorCode 的地方也越来越多。有一天,我正准备复用一个底层图片处理函数时,突然碰到了一个问题。

    我看到了一段让我非常纠结的代码:

    process_image 函数会尝试解析一个文件对象,如果该对象不能被作为图片正常打开,就抛出 error_codes.INVALID_IMAGE_UPLOADED(APIErrorCode子类) 异常,从而给调用方返回错误代码 JSON。

    让我给你从头理理这段代码。最初编写 process_image 时,我虽然把它放在了 util.image 模块里,但当时调这个函数的地方就只有 “处理用户上传图片的 POST 请求” 而已。为了偷懒,我让函数直接抛出 APIErrorCode 异常来完成了错误处理工作。

    再来说当时的问题。那时我需要写一个在后台运行的批处理图片脚本,而它刚好可以复用 process_image 函数所实现的功能。但这时不对劲的事情出现了,如果我想复用该函数,那么:

    • 我必须去捕获一个名为 INVALID_IMAGE_UPLOADED 的异常

      • 哪怕我的图片根本就不是来自于用户上传

    • 我必须引入 APIErrorCode 异常类作为依赖来捕获异常

      • 哪怕我的脚本和 Django API 根本没有任何关系

    这就是异常类抽象层级不一致导致的结果。APIErrorCode 异常类的意义,在于表达一种能够直接被终端用户(人)识别并消费的 “错误代码”。它在整个项目里,属于最高层的抽象之一。但是出于方便,我们却在底层模块里引入并抛出了它。这打破了 image.processor 模块的抽象一致性,影响了它的可复用性和可维护性。

    这类情况属于 “模块抛出了高于所属抽象层级的异常”。避免这类错误需要注意以下几点:

    • 让模块只抛出与当前抽象层级一致的异常

      • 比如 image.processer 模块应该抛出自己封装的 ImageOpenError 异常

    • 在必要的地方进行异常包装与转换

      • 比如,应该在贴近高层抽象(视图 View 函数)的地方,将图像处理模块的 ImageOpenError 低级异常包装转换为 APIErrorCode 高级异常

    修改后的代码:

    除了应该避免抛出高于当前抽象级别的异常外,我们同样应该避免泄露低于当前抽象级别的异常。

    如果你用过 requests 模块,你可能已经发现它请求页面出错时所抛出的异常,并不是它在底层所使用的 urllib3 模块的原始异常,而是通过 requests.exceptions 包装过一次的异常。

    这样做同样是为了保证异常类的抽象一致性。因为 urllib3 模块是 requests 模块依赖的底层实现细节,而这个细节有可能在未来版本发生变动。所以必须对它抛出的异常进行恰当的包装,避免未来的底层变更对 requests 用户端错误处理逻辑产生影响。

     

    3. 异常处理不应该喧宾夺主

    在前面我们提到异常捕获要精准、抽象级别要一致。但在现实世界中,如果你严格遵循这些流程,那么很有可能会碰上另外一个问题:异常处理逻辑太多,以至于扰乱了代码核心逻辑。具体表现就是,代码里充斥着大量的 try、 except、 raise 语句,让核心逻辑变得难以辨识。

    让我们看一段例子:

    这是一个处理用户上传头像的视图函数。这个函数内做了三件事情,并且针对每件事都做了异常捕获。如果做某件事时发生了异常,就返回对用户友好的错误到前端。

    这样的处理流程纵然合理,但是显然代码里的异常处理逻辑有点 “喧宾夺主” 了。一眼看过去全是代码缩进,很难提炼出代码的核心逻辑。

    早在 2.5 版本时,Python 语言就已经提供了对付这类场景的工具:“上下文管理器(context manager)”。上下文管理器是一种配合 with 语句使用的特殊 Python 对象,通过它,可以让异常处理工作变得更方便。

    那么,如何利用上下文管理器来改善我们的异常处理流程呢?让我们直接看代码吧。

    在上面的代码里,我们定义了一个名为 raise_api_error 的上下文管理器,它在进入上下文时什么也不做。但是在退出上下文时,会判断当前上下文中是否抛出了类型为 self.captures 的异常,如果有,就用 APIErrorCode 异常类替代它。

    使用该上下文管理器后,整个函数可以变得更清晰简洁:

    Hint:建议阅读 PEP 343 -- The "with" Statement | Python.org,了解与上下文管理器有关的更多知识。

    模块 contextlib 也提供了非常多与编写上下文管理器相关的工具函数与样例。

     

    总结

    在这篇文章中,我分享了与异常处理相关的三个建议。最后再总结一下要点:

    • 只捕获可能会抛出异常的语句,避免含糊的捕获逻辑

    • 保持模块异常类的抽象一致性,必要时对底层异常类进行包装

    • 使用 “上下文管理器” 可以简化重复的异常处理逻辑

     

    展开全文
  • 精品文档 你我共享 Python异常处理能力是很强大的可向用户准确反馈出错信息本文将对 Python 异常处理体系进行简 单介绍 Python 内建异常体系结构 The class hierarchy for built-in exceptions is BaseException ...
  • 我们自定义了一个异常类,叫做CustomerError,继承自BaseException这个Python异常错误类型的基类 然后定义其__init__方法,并用一个变量接受传入的错误信息。 __init__方法里可以什么都不做,用一个pass...

    我曾经帮很多人修过电脑,排除硬件问题、排除系统问题、网络问题等等。

    在修电脑的过程中比如电脑无法开机,我就会假定它某个配件已经坏掉了,就先从电源开始排查起、CPU、内存、主板等等一个一个的测试,看看究竟是哪一个配件有问题。

    如果操作系统系统速度变慢同样也可以用类似的办法,先看当前占用进程有没有不必要启动的、是否有病毒、系统是否没有优化等等。

    实际上在编程过程中,我们一样会假定某段代码某个功能可能会出现问题的方式来编写我们的代码。

    今天我们主要来讲讲Python语言的异常处理相关知识。

    本章知识点:

    • 异常捕获

    • 主动抛出异常

    • 自定义异常

    异常捕获:

    首先我们来看一个例子:

    我通过 print(a) 让Python解释器给我报了一个异常,其中包含错误信息的所有上下文信息,代码路径、错误代码内容、错误信息等等。

    print(a)这个语句错误的原因是在于a这个变量没有定义 (NameError: name ‘a’ is not defined),这在我写代码之前就清楚。

    然而在实际编程过程中,随着代码量的增加,我们有的时候并不确定某个变量是否已经被赋值成功,如果变量未被成功的赋值,程序还是按原计划对其进行操作时可能就会直接报错。

    要解决这个问题有两个办法:

    1. 对变量进行操作(例如加减法)之前检查它的值是否已经存在,如果不存在,就告诉用户

    2. 捕获该异常,并且告诉用户

    这两种办法的结果其实都是会告诉用户错误信息,在结果上并没有太大的变化,今天我们主要讲第二种,异常自动捕获的方式。

    现在我们改造一下刚才的代码,把异常捕获到然后自定义处理方式:

    以上的代码例子展示我们通过异常捕获 try except的语法把错误捕获到,并且自定义了其输出内容。

    让我们来解释一下这段代码:

    • try用于定义一个异常捕获的语法块。

    • try缩进的区块里,我们可以正常写我们想要实现的代码。

    • except区块里,我们定义了如果程序报错后所要执行的代码,在本例子中就是直接打印报错信息 (报错信息:name ‘a’ is not defined)。

    • except后面的Exception as e 的用处是用于定义错误信息类型(Exception),并且将错误信息赋值给变量e。

    通过在代码任意位置使用try…except语法,我们可以设置多个try…except的代码块,如果在try中程序正常执行没有报错,那么程序就会跳过except区块,正常执行之后的代码。

    异常类型:

    我们刚才用到Exception这个异常类型,它在Python中是常规错误的基类,如果我们对可能出错的类型不能确定时就可以使用到它,但是一般不建议这么做

    不直接使用Exception的理由是我们在捕获到异常时,总是希望能够对异常进行明确的报错或者处理,如果所有错误都是Exception类型,我们其实也不知道程序究竟是在哪里出了错。

    举个例子来说明这个问题。

    通过上面这个例子我们可以学到两件事情

    1. except和try是一对多的,有一个try语句,可以有1个或多个 except语句,其用处是定义任意个异常类型和相关的处理代码

    2. 当异常捕获发生后,程序会中断执行,停留在第一个异常报错的位置。在本例子中因为我们import xxx实际上是引入了一个不存在的模块名,所以程序报错 No module named ‘xxx’,其错误类型是ImportError

    现在我们尝试把import xxx去掉试试。

    现在程序报了类型错误(TypeError),因为int类型的数据无法和str类型的数字进行加法操作。

    下面我给一个Python的常见异常错误类型表,供大家参考。

    主动抛出异常:

    上面我们讲到了Python如何被动捕获异常,现在我们来讲讲主动抛出异常的方法。

    为什么要主动抛出异常?

    通常我们通过try except捕获的异常叫做被动捕获,它其实是需要程序员进行处理的,比如对错误的变量内容做一些改正让其继续执行。但是主动抛出异常通常不需要再进行处理,程序员已经确定这个地方必须抛出异常给用户,并且中断程序执行,基于这种情况下程序员就不用再对异常进行处理了。

    来看一个例子:

    在这段代码里,我们定义了a为一个整型的数字。

    然后通过instance()内部函数判断a如果不是字符串类型的情况下,就通过raise语句主动抛出一个异常,报错内容也是我们自定义的,其作用就是直接告诉用户,数据出错了。

    有朋友会提一个问题,你自己定义的a = 1,它明明是整型数字,你还拿去判断它是不是字符串,这不是多此一举吗?它是不是字符串你心里没点数吗?

    没错,之所以你会有这个问题是因为我们的例子太简单,假设变量a的内容是来自于另一个模块呢?或者是来自于爬虫从网络上抓取下来的数据?这个时候我们根本不知道a可能是什么内容,那么就必须用到异常处理机制了。

    raise的语法很简单:

    raise [exceptionName [(reason)]]

    在它后面跟上想要抛出的异常类型即可,如果有必要写上错误内容的话,就传进去:

    raise ValueError(“a必须是字符串”)

    另外raise语句其实也可以和 try except结合起来使用:

    上面这个例子展现了程序如何主动抛出异常,再由except捕获并打印错误信息。

    自定义异常:

    其实我们刚才了解到所有异常错误类型其实都是一个类 (class ),那么我们同样可以自定义一个异常类,以便于在程序里使用。

    通过以上的代码例子:

    • 我们自定义了一个异常类,叫做CustomerError,继承自BaseException这个Python异常错误类型的基类

    • 然后定义其__init__方法,并用一个变量接受传入的错误信息。

    • __init__方法里可以什么都不做,用一个pass占位即可,因为CustomerError类是继承自BaseException的,它天生具有BaseException的所有特性。

    • 最后我们抛出一个CustomerError异常,并传了一个字符串内容"自定义异常"进去,由except 捕获到这个异常并输出异常内容。

    总结:

    通过自定义异常,我们可以不用拘泥于Python自带的异常错误类型,定义更多自己想要的错误类型,精确的控制出错的时机和处理方式。

    最后通过一个思维导图来展示异常处理的相关知识点。


    推荐我们的Python学习扣qun:784758214 ,看看前辈们是如何学习的!从基础的python脚本到web开发、爬虫、django、数据挖掘等【PDF,实战源码】,零基础到项目实战的资料都有整理。送给每一位python的小伙伴!每天都有大牛定时讲解Python技术,分享一些学习的方法和需要注意的小细节,点击加入我们的 python学习者聚集地

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 211,894
精华内容 84,757
关键字:

python异常处理

python 订阅