1. 异常的概念

  • 程序在运行时,如果 Python 解释器 遇到 到一个错误,会停止程序的执行,并且提示一些错误信息,这就是 异常

  • 程序停止执行并且提示错误信息 这个动作,我们通常称之为:抛出(raise)异常

在程序开发的世界里,就像是在一场充满了各种特殊情况的冒险中前行。我们尽管努力,但很难面面俱到地处理每一个突发事件。这就像是在玩一场没有尽头的“捉迷藏”游戏,我们需要找到一种方法来集中处理这些意外情况,以保证我们的程序能够稳定而健壮地运行下去。

幸运的是,在Python的世界里,我们有一个强大的工具——异常捕获。就像是一张安全网,它可以帮助我们捕捉并处理那些意外情况,让我们的程序能够在困境中挺过来。

当我们遇到一个特殊情况时,就像是在玩一场“猫捉老鼠”的游戏,我们可以使用tryexcept关键字来捕获异常。这就像是在给我们的程序穿上一件坚固的盔甲,让它能够抵挡住各种意外的攻击。

通过异常捕获,我们可以在程序遇到问题时,优雅地处理它们,而不是让程序崩溃。这就像是在玩一场“躲避球”的游戏,我们可以灵活地躲避那些突如其来的球,保持我们的程序的稳定性和健壮性。

2. 异常类型

1)Python内置异常

哇哦!Python的异常处理能力真是强大无比!它提供了许多内置异常,可以向我们准确地反馈出错信息。在Python中,异常也是一种对象,我们可以对它进行操作。所有内置异常都是从BaseException这个基类继承而来的,不过我们自己定义的异常类并不直接继承BaseException,而是继承自Exception。这些异常类都被放在了exceptions模块中,而且Python会自动将所有异常名称放在内建命名空间中,所以我们在程序中使用异常时,无需导入exceptions模块。

当一个异常被引发且没有被捕捉到时,如果这个异常不是SystemExit,那么程序就会终止。如果我们在交互式会话中遇到一个未被捕捉的SystemExit异常,会话也会终止。所以,小心处理异常,避免让程序或会话提前结束哦!

内置异常类的层次结构如下:

BaseException  # 所有异常的基类
 +-- SystemExit  # 解释器请求退出
 +-- KeyboardInterrupt  # 用户中断执行(通常是输入^C)
 +-- GeneratorExit  # 生成器(generator)发生异常来通知退出
 +-- Exception  # 常规异常的基类
      +-- StopIteration  # 迭代器没有更多的值
      +-- StopAsyncIteration  # 必须通过异步迭代器对象的__anext__()方法引发以停止迭代
      +-- ArithmeticError  # 各种算术错误引发的内置异常的基类
      |    +-- FloatingPointError  # 浮点计算错误
      |    +-- OverflowError  # 数值运算结果太大无法表示
      |    +-- ZeroDivisionError  # 除(或取模)零 (所有数据类型)
      +-- AssertionError  # 当assert语句失败时引发
      +-- AttributeError  # 属性引用或赋值失败
      +-- BufferError  # 无法执行与缓冲区相关的操作时引发
      +-- EOFError  # 当input()函数在没有读取任何数据的情况下达到文件结束条件(EOF)时引发
      +-- ImportError  # 导入模块/对象失败
      |    +-- ModuleNotFoundError  # 无法找到模块或在在sys.modules中找到None
      +-- LookupError  # 映射或序列上使用的键或索引无效时引发的异常的基类
      |    +-- IndexError  # 序列中没有此索引(index)
      |    +-- KeyError  # 映射中没有这个键
      +-- MemoryError  # 内存溢出错误(对于Python 解释器不是致命的)
      +-- NameError  # 未声明/初始化对象 (没有属性)
      |    +-- UnboundLocalError  # 访问未初始化的本地变量
      +-- OSError  # 操作系统错误,EnvironmentError,IOError,WindowsError,socket.error,select.error和mmap.error已合并到OSError中,构造函数可能返回子类
      |    +-- BlockingIOError  # 操作将阻塞对象(e.g. socket)设置为非阻塞操作
      |    +-- ChildProcessError  # 在子进程上的操作失败
      |    +-- ConnectionError  # 与连接相关的异常的基类
      |    |    +-- BrokenPipeError  # 另一端关闭时尝试写入管道或试图在已关闭写入的套接字上写入
      |    |    +-- ConnectionAbortedError  # 连接尝试被对等方中止
      |    |    +-- ConnectionRefusedError  # 连接尝试被对等方拒绝
      |    |    +-- ConnectionResetError    # 连接由对等方重置
      |    +-- FileExistsError  # 创建已存在的文件或目录
      |    +-- FileNotFoundError  # 请求不存在的文件或目录
      |    +-- InterruptedError  # 系统调用被输入信号中断
      |    +-- IsADirectoryError  # 在目录上请求文件操作(例如 os.remove())
      |    +-- NotADirectoryError  # 在不是目录的事物上请求目录操作(例如 os.listdir())
      |    +-- PermissionError  # 尝试在没有足够访问权限的情况下运行操作
      |    +-- ProcessLookupError  # 给定进程不存在
      |    +-- TimeoutError  # 系统函数在系统级别超时
      +-- ReferenceError  # weakref.proxy()函数创建的弱引用试图访问已经垃圾回收了的对象
      +-- RuntimeError  # 在检测到不属于任何其他类别的错误时触发
      |    +-- NotImplementedError  # 在用户定义的基类中,抽象方法要求派生类重写该方法或者正在开发的类指示仍然需要添加实际实现
      |    +-- RecursionError  # 解释器检测到超出最大递归深度
      +-- SyntaxError  # Python 语法错误
      |    +-- IndentationError  # 缩进错误
      |         +-- TabError  # Tab和空格混用
      +-- SystemError  # 解释器发现内部错误
      +-- TypeError  # 操作或函数应用于不适当类型的对象
      +-- ValueError  # 操作或函数接收到具有正确类型但值不合适的参数
      |    +-- UnicodeError  # 发生与Unicode相关的编码或解码错误
      |         +-- UnicodeDecodeError  # Unicode解码错误
      |         +-- UnicodeEncodeError  # Unicode编码错误
      |         +-- UnicodeTranslateError  # Unicode转码错误
      +-- Warning  # 警告的基类
           +-- DeprecationWarning  # 有关已弃用功能的警告的基类
           +-- PendingDeprecationWarning  # 有关不推荐使用功能的警告的基类
           +-- RuntimeWarning  # 有关可疑的运行时行为的警告的基类
           +-- SyntaxWarning  # 关于可疑语法警告的基类
           +-- UserWarning  # 用户代码生成警告的基类
           +-- FutureWarning  # 有关已弃用功能的警告的基类
           +-- ImportWarning  # 关于模块导入时可能出错的警告的基类
           +-- UnicodeWarning  # 与Unicode相关的警告的基类
           +-- BytesWarning  # 与bytes和bytearray相关的警告的基类
           +-- ResourceWarning  # 与资源使用相关的警告的基类。被默认警告过滤器忽略。

2)requests爬虫模块内置异常类

嘿,你知道吗?在使用Python的requests模块进行爬虫时,有时候会遇到各种网络问题,比如请求超时、连接错误等等。这些问题可能会导致我们的程序崩溃,这可不是我们想要的结果啊。

幸运的是,requests模块内置了一些异常类,可以帮助我们处理这些问题。当然,为了让我们的程序能够优雅地处理这些异常,我们需要使用try...except语句来捕获并处理这些错误。

那么,requests模块可能会抛出哪些异常呢?让我来告诉你:

  • requests.exceptions.RequestException:这是所有requests模块异常的基类,如果你想捕获所有的requests异常,可以使用这个类。

  • requests.exceptions.Timeout:当请求超时时,就会抛出这个异常。你可以在try...except语句中使用Timeout来捕获这个异常。

  • requests.exceptions.ConnectionError:当请求发生连接错误时,就会抛出这个异常。同样地,你可以使用ConnectionError来捕获这个异常。

  • requests.exceptions.HTTPError:如果请求返回的状态码不是200,就会抛出这个异常。你可以使用HTTPError来捕获这个异常。

  • requests.exceptions.TooManyRedirects:如果请求重定向次数过多,就会抛出这个异常。你可以使用TooManyRedirects来捕获这个异常。

  • 还有其他一些异常类,比如SSLError、ProxyError等等,你可以根据具体情况来选择捕获。

现在,你知道了requests模块内置的异常类,可以在你的爬虫程序中使用try...except语句来捕获并处理这些异常了。这样,即使遇到网络问题,你的程序也能够继续运行下去,不会崩溃哦!加油!

requests模块内置异常类的层次结构如下:

IOError
 +-- RequestException  # 处理不确定的异常请求
      +-- HTTPError  # HTTP错误
      +-- ConnectionError  # 连接错误
      |    +-- ProxyError  # 代理错误
      |    +-- SSLError  # SSL错误
      |    +-- ConnectTimeout(+-- Timeout)  # (双重继承,下同)尝试连接到远程服务器时请求超时,产生此错误的请求可以安全地重试。
      +-- Timeout  # 请求超时
      |    +-- ReadTimeout  # 服务器未在指定的时间内发送任何数据
      +-- URLRequired  # 发出请求需要有效的URL
      +-- TooManyRedirects  # 重定向太多
      +-- MissingSchema(+-- ValueError) # 缺少URL架构(例如http或https)
      +-- InvalidSchema(+-- ValueError) # 无效的架构,有效架构请参见defaults.py
      +-- InvalidURL(+-- ValueError)  # 无效的URL
      |    +-- InvalidProxyURL  # 无效的代理URL
      +-- InvalidHeader(+-- ValueError)  # 无效的Header
      +-- ChunkedEncodingError  # 服务器声明了chunked编码但发送了一个无效的chunk
      +-- ContentDecodingError(+-- BaseHTTPError)  # 无法解码响应内容
      +-- StreamConsumedError(+-- TypeError)  # 此响应的内容已被使用
      +-- RetryError  # 自定义重试逻辑失败
      +-- UnrewindableBodyError  # 尝试倒回正文时,请求遇到错误
      +-- FileModeWarning(+-- DeprecationWarning)  # 文件以文本模式打开,但Requests确定其二进制长度
      +-- RequestsDependencyWarning  # 导入的依赖项与预期的版本范围不匹配
 
Warning
 +-- RequestsWarning  # 请求的基本警告

3)自定义异常

用户自定义异常类型非常简单,只需要创建一个类并继承自Exception类即可。你可以根据自己的需求来定义这个类的主体内容,就像你参考官方异常类一样。这样一来,你就可以在程序中使用自定义异常来处理特定的错误情况了。记住,使用raise关键字可以抛出你自定义的异常,让程序在遇到特定的情况时执行相应的操作。

所以,如果你觉得程序中需要一个特殊的异常类型来处理某些情况,不妨试试自定义异常。你可以给你的异常起一个有趣的名字,比如OutOfCoffeeException(咖啡不够了异常)或者KeyboardCatException(键盘被猫占领异常)。然后,你可以在程序中使用raise关键字抛出这个异常,让程序执行相应的操作。这样一来,你的程序就会变得更加有趣和个性化了!

class OutOfCoffeeException(Exception):
    pass

class KeyboardCatException(Exception):
    pass

def drink_coffee(cups):
    if cups == 0:
        raise OutOfCoffeeException("Oh no! I'm out of coffee!")
    else:
        print("Enjoy your coffee!")

def play_with_cat():
    raise KeyboardCatException("Oops! The keyboard is occupied by a cat!")

try:
    drink_coffee(0)
except OutOfCoffeeException as e:
    print(e)

try:
    play_with_cat()
except KeyboardCatException as e:
    print(e)

2. 捕获异常

1)简单的捕获异常语法

  • 在程序开发中,如果对某些代码的执行不能确定是否正确,可以使用 try 来捕获异常。通过使用 try 块,我们可以在代码中指定可能会出现异常的部分,并在出现异常时执行相应的处理逻辑。这样可以避免程序在遇到错误时崩溃,并且可以提供更好的错误处理和用户体验。下面是一个简单的示例:

    try:
        # 可能会出现异常的代码
        result = 10 / 0
    except ZeroDivisionError:
        # 处理 ZeroDivisionError 异常
        print("除数不能为零!")
    except Exception as e:
        # 处理其他异常
        print("发生了一个未知的异常:", e)
    else:
        # 如果没有发生异常,则执行这里的代码
        print("计算结果:", result)
    finally:
        # 无论是否发生异常,都会执行这里的代码
        print("程序执行完毕!")
    

    在上面的示例中,我们使用 try 块来尝试执行可能会出现异常的代码,如果出现了 ZeroDivisionError 异常,会执行对应的 except 块中的代码,如果出现了其他异常,会执行相应的 except 块中的代码。如果没有发生异常,则会执行 else 块中的代码。无论是否发生异常,都会执行 finally 块中的代码。通过使用 try 块和相应的异常处理逻辑,我们可以更好地控制程序的执行流程,提高代码的健壮性和可靠性。

  • 捕获异常最简单的语法格式:

try:
    尝试执行的代码
except:
    出现错误的处理
  • try 尝试,下方编写要尝试代码,不确定是否能够正常执行的代码

  • except 如果不是,下方编写尝试失败的代码

简单异常捕获演练 —— 要求用户输入整数

try:
    # 提示用户输入一个数字
    num = int(input("请输入数字:"))
except:
    print("请输入正确的数字")

2)错误类型捕获

  • 在程序执行的过程中,可能会遇到各种各样的异常情况。这就好像是在玩一个游戏,你永远不知道下一个关卡会有什么样的障碍等着你。有时候,你需要根据不同类型的异常,做出不同的应对措施,就像是在游戏中选择不同的武器来对付不同的敌人一样。

    在Python中,我们可以使用异常处理机制来捕获这些错误类型。这就好像是在游戏中有一个特殊的技能,可以帮助你在遇到困难时保持冷静,并且做出正确的反应。

    使用tryexcept关键字,我们可以编写代码来捕获可能出现的异常,并且在出现异常时执行特定的代码块。这就好像是在游戏中,当你遇到一个敌人时,你可以选择使用特定的技能来对付它。

    下面是一个简单的示例代码:

    try:
        # 可能会出现异常的代码块
        # ...
    except 错误类型1:
        # 针对错误类型1的响应代码
        # ...
    except 错误类型2:
        # 针对错误类型2的响应代码
        # ...
    

    在这个示例中,我们使用try关键字来包裹可能会出现异常的代码块。如果在执行这段代码时出现了错误类型1的异常,那么程序会跳转到对应的except代码块,并执行其中的代码。同样地,如果出现了错误类型2的异常,程序会跳转到对应的except代码块。

    通过这种方式,我们可以根据不同类型的异常,灵活地做出不同的响应。这就好像是在游戏中,你可以根据不同的敌人选择不同的策略来对付它们。

    所以,记住,在编写程序时,不要忘记考虑可能出现的异常情况,并且使用异常处理机制来捕获和处理这些异常。这样,你的程序就能更加健壮和可靠,就像是在游戏中,你可以应对各种各样的挑战一样!

  • 当 Python 解释器 抛出异常 时,最后一行错误信息的第一个单词,就是错误类型

异常类型捕获演练 —— 要求用户输入整数

需求

  • 1.提示用户输入一个整数

  • 2.使用 8 除以用户输入的整数并且输出

try:
    num = int(input("请输入整数:"))
    result = 8 / num
    print(result)
except ValueError:
    print("请输入正确的整数")
except ZeroDivisionError:
    print("除 0 错误")

捕获未知错误

  • 在开发时,要预判到所有可能出现的错误,还是有一定难度的

  • 如果希望程序 无论出现任何错误,都不会因为 Python 解释器 抛出异常而被终止,可以再增加一个 except

语法如下

except Exception as result:
    print("未知错误 %s" % result)

3)异常捕获完整语法

  • 在实际开发中,为了能够处理复杂的异常情况,完整的异常语法如下:

提示

有关完整语法的应用场景,在后续学习中,结合实际的案例会更好理解
现在先对这个语法结构有个印象即可

try:
    # 尝试执行的代码
    pass
except 错误类型1:
    # 针对错误类型1,对应的代码处理
    pass
except 错误类型2:
    # 针对错误类型2,对应的代码处理
    pass
except (错误类型3, 错误类型4):
    # 针对错误类型3 和 4,对应的代码处理
    pass
except Exception as result:
    # 打印错误信息
    print(result)
else:
    # 没有异常才会执行的代码
    pass
finally:
    # 无论是否有异常,都会执行的代码
    print("无论是否有异常,都会执行的代码")

    else 只有在没有异常时才会执行的代码

    finally 无论是否有异常,都会执行的代码

    之前一个演练的 完整捕获异常 的代码如下:

try:
    num = int(input("请输入整数:"))
    result = 8 / num
    print(result)
except ValueError:
    print("请输入正确的整数")
except ZeroDivisionError:
    print("除 0 错误")
except Exception as result:
    print("未知错误 %s" % result)
else:
    print("正常执行")
finally:
    print("执行完成,但是不保证正确")
  • else 只有在没有异常时才会执行的代码

  • finally 无论是否有异常,都会执行的代码

  • 之前一个演练的 完整捕获异常 的代码如下:

try:
    num = int(input("请输入整数:"))
    result = 8 / num
    print(result)
except ValueError:
    print("请输入正确的整数")
except ZeroDivisionError:
    print("除 0 错误")
except Exception as result:
    print("未知错误 %s" % result)
else:
    print("正常执行")
finally:
    print("执行完成,但是不保证正确")

3. 异常的传递

  • 异常的传递 —— 当某个函数/方法执行时出现异常,它会把异常传递给调用它的那个函数/方法,就像传递热土豆一样,谁接到谁处理。

  • 如果异常一直传递到主程序,但是主程序没有对异常进行处理,那么程序就会被终止,就像烫手的土豆被扔掉一样可惜。

提示

  • 在开发中,我们可以在主函数中增加异常捕获的机制,就像准备一个接住热土豆的保护手套一样。

  • 而在主函数中调用的其他函数,只要出现异常,就会把异常传递到主函数的异常捕获机制中,就像热土豆被传递到保护手套里一样。

  • 这样一来,我们就不需要在代码中到处添加大量的异常捕获机制,能够保证代码的整洁,就像保持手套的干净一样。

需求

  • 定义一个函数 demo1(),它会提示用户输入一个整数并返回该整数。

  • 定义一个函数 demo2(),它会调用 demo1(),就像一个人把热土豆传递给另一个人一样。

  • 在主程序中调用 demo2(),就像一个人把热土豆传递给主程序一样。

def demo1():
    return int(input("请输入一个整数:"))


def demo2():
    return demo1()

try:
    print(demo2())
except ValueError:
    print("请输入正确的整数")
except Exception as result:
    print("未知错误 %s" % result)

4. 抛出异常

1)应用场景

  • 在开发中,除了代码执行出错 Python 解释器会抛出异常之外,

  • 还可以根据应用程序特有的业务需求主动抛出异常。

2)使用 raise 关键字

在 Python 中,我们可以使用 raise 关键字来抛出异常。抛出异常的语法如下:

raise 异常类型("异常描述")

其中,异常类型可以是 Python 内置的异常类型,也可以是自定义的异常类型。异常描述是一个可选的参数,用于描述异常的具体信息。

3)示例

下面是一个示例,演示了如何使用 raise 关键字抛出异常:

def divide(x, y):
    if y == 0:
        raise ZeroDivisionError("除数不能为零")
    return x / y

try:
    result = divide(10, 0)
    print(result)
except ZeroDivisionError as e:
    print("发生了异常:", e)

在上面的示例中,如果除数 y 的值为零,就会抛出 ZeroDivisionError 异常,并输出异常信息。在 try 块中调用 divide 函数时,由于除数为零,所以会抛出异常。然后在 except 块中捕获并处理该异常。

通过抛出异常,我们可以在程序中主动引发错误,从而提醒开发者或用户注意并处理相应的情况。

4)示例

示例:提示用户 输入密码,如果 长度少于 8,抛出 异常

注意

  • 当前函数 只负责 提示用户输入密码,如果 密码长度不正确需要其他的函数进行额外处理

  • 因此可以 抛出异常,由其他需要处理的函数 捕获异常

2)抛出异常

  • Python 中提供了一个 Exception 异常类

  • 在开发时,如果满足 特定业务需求时,希望 抛出异常,可以:

    • 1.创建 一个 Exception 的 对象

    • 2.使用 raise 关键字 抛出 异常对象

需求

  • 定义 input_password 函数,提示用户输入密码

  • 如果用户输入长度 < 8,抛出异常

  • 如果用户输入长度 >=8,返回输入的密码

def input_password():

    # 1. 提示用户输入密码
    pwd = input("请输入密码:")

    # 2. 判断密码长度,如果长度 >= 8,返回用户输入的密码
    if len(pwd) >= 8:
        return pwd

    # 3. 密码长度不够,需要抛出异常
    # 1> 创建异常对象 - 使用异常的错误信息字符串作为参数
    ex = Exception("密码长度不够")

    # 2> 抛出异常对象
    raise ex

try:
    user_pwd = input_password()
    print(user_pwd)
except Exception as result:
    print("发现错误:%s" % result)