跳至主要內容

Python异常处理(七)

apzs...大约 13 分钟

Python异常处理(七)

python学习之旅(七) 学习汇总入口【Python】学习汇总(3万字+思维导图)open in new window 笔记PDF下载:知识笔记:Python异常处理(七)open in new window 文末附带全文概览思维导图 写作不易,如果您觉得写的不错,欢迎给博主来一波点赞、收藏~让博主更有动力吧!

一.什么是异常

程序运行的过程中出现了错误

  • 定义:在程序运行中,检测到一个错误,程序中止运行并且出现了一些错误的提示,也称作BUG
  • 例如:读取一个不存在的文件f = open("C:/code/观止.txt", "r")
img
img

二.为什么要捕获异常

避免程序中止,提前准备处理可能出现的异常

  • 在真实工作中, 我们肯定不能因为一个小的BUG就让整个程序全部奔溃,而是对BUG进行提醒, 整个程序继续运行

三.如何捕获异常

在可能出现异常的地方,做好提前准备,当真的出现异常的时候,可以有后续手段。

(1) 捕获常规异常

  • 基本语法:
try:
    可能发生错误的代码
except:
    如果出现异常执行的代码
    
# 未发生错误try全部代码都会执行
# 未发生错误不会执行except中的代码
# 发生错误try中只会执行到报错行为止的代码
# 发生错误会执行except中的代码
  • 使用示例:

    • 首次执行,文件不存在,程序未报错中止,而是转而执行except中代码,创建文件
    try:
        print("r模式打开") # 执行
        f = open("C:/code/观止.txt", "r") # 报错
        print("r模式打开") # 不执行
    except:
        print("w模式打开") # 执行
        f = open("C:/code/观止.txt", "w") # 执行
        print("w模式打开") # 执行
    
img
img
  • 第二次执行,文件存在,程序无异常,只执行try中代码
try:
    print("r模式打开") # 执行
    f = open("C:/code/观止.txt", "r") # 执行
    print("r模式打开") # 执行
except:
    print("w模式打开") # 不执行
    f = open("C:/code/观止.txt", "w") # 不执行
    print("w模式打开") # 不执行
img
img

(2) 捕获特定异常

  • 如果尝试执行的代码的异常类型和要捕获的异常类型不一致,则无法捕获异常。
  • 基本语法:
try:
    可能发生错误的代码
except 待捕获异常名 as 别名:
    如果出现异常执行的代码
  • 例如:

    • 捕获未定义变量产生的错误
    try:
        print(name) # 未定义变量,报错
    except NameError as e:
        print('name变量名称未定义错误')
    
img
img
  • 同样的代码却无法捕获处理找不到文件异常
try:
    f = open("C:/code/study.txt", "r") # 文件不存在,报错
except NameError as e:
    print('文件不存在')
img
img

(3) 捕获多个异常

  • 格式一:当待捕获异常名为Exception可以捕获所有类型异常,作用与(1)一致

    • 例如:
    try:
        f = open("C:/code/study.txt", "r")
    except Exception as e:
        print('文件不存在')
    
img
img
  • 格式二:把要捕获的异常类型的名字,放到except 后,并使用元组的方式进行书写。
  • 基本格式:
try:
    可能发生错误的代码
except (异常名1,异常名2) as 别名:
    如果出现异常执行的代码
  • 使用示例:
# 示例一:
try:
    f = open("C:/code/study.txt", "r")
except (FileNotFoundError, NameError) as e:
    print('文件不存在')
# 示例二:    
try:
    print(name)
except (FileNotFoundError, NameError) as e:
    print('名称未定义')
  • 指定的两种异常都能捕获,未指定的无法捕获到
img
img

(4) 其他用法

(4.1) 打印异常信息
  • 异常描述信息存贮在别名中,可以通过打印别名获取
  • 使用示例:
try:
    print(num) # 未定义,报错
except (NameError, ZeroDivisionError) as e:
    print(e) # 打印 name 'num' is not defined
img
img
(4.2) 异常else
  • else表示的是如果没有异常要执行的代码。

  • 使用示例:

    • 出现异常,打印结果与(4.2)一致
    try:
        print(num) # 未定义,报错
    except (NameError, ZeroDivisionError) as e:
        print(e) # 打印 name 'num' is not defined
    else:
        print("无异常") # 有异常,不执行
    
    • 无异常
try:
    print("正常") # 不报错
except (NameError, ZeroDivisionError) as e:
    print(e) # 不执行
else:
    print("无异常") # 执行
img
img
(4.3) 异常finally
  • finally表示的是无论是否异常都要执行的代码

  • 使用示例:

    • 之前提过,如果open文件却一直未close且程序未中止,将一直占用文件无法操作
    • 如果打开文件后发生异常,未close也将导致一直占用,因此可选择在finally中close
    global f
    try:
        f = open("C:/code/aaa.txt", "r")
    except Exception as e:
        print(e)  
    finally:
        f.close() # 一定会执行close操作
    

四.异常的传递

异常是具有传递性的(向上一级抛出)

  • 当函数调用链中出现异常,如果所有函数都没有捕获异常的时候, 程序就会报错
img
img
  • 利用异常具有传递性的特点, 当我们想要保证程序不会因为异常崩溃的时候, 就可以在主函数中设置异常捕获, 由于无论在整个程序哪里发生异常, 最终都会传递到主函数中, 这样就可以确保所有的异常都会被统一捕获

五. 抛出异常

异常是什么

在 Python 中,异常是程序执行过程中发生的错误事件,它会中断程序的正常执行流程。Python 内置了多种异常类型,如 SyntaxError(语法错误)、TypeError(类型错误)、ValueError(值错误)等。当程序遇到异常时,如果没有进行相应的处理,程序将会终止并输出错误信息。

抛出异常的作用

抛出异常是一种主动发现并报告错误的方式。当程序执行到某个条件不满足或出现错误时,开发者可以手动抛出一个异常,让调用者知道发生了什么问题,并进行相应的处理。这样可以避免程序在错误状态下继续运行,导致更严重的问题。

使用方法

抛出内置异常

在 Python 中,可以使用 raise 语句来抛出异常。raise 语句的基本语法如下:

raise ExceptionType("异常信息")

以下是一个简单的示例,当输入的数字为负数时,抛出 ValueError 异常:

def check_positive(num):
    if num < 0:
        raise ValueError("输入的数字不能为负数")
    return num
 
try:
    result = check_positive(-5)
except ValueError as e:
    print(f"捕获到异常: {e}")
自定义异常

除了使用内置异常类型,开发者还可以自定义异常类。自定义异常类需要继承自 Python 的内置异常类,通常继承自 Exception 类。以下是一个自定义异常类的示例:

class MyCustomError(Exception):
    pass
 
def divide_numbers(a, b):
    if b == 0:
        raise MyCustomError("除数不能为零")
    return a / b
 
try:
    result = divide_numbers(10, 0)
except MyCustomError as e:
    print(f"捕获到自定义异常: {e}")

常见实践

在函数中抛出异常

在函数中抛出异常是一种常见的实践。当函数接收到无效的参数或无法完成其任务时,可以抛出异常,让调用者处理。例如:

def get_element(lst, index):
    if index < 0 or index >= len(lst):
        raise IndexError("索引超出范围")
    return lst[index]
 
my_list = [1, 2, 3]
try:
    element = get_element(my_list, 5)
except IndexError as e:
    print(f"捕获到异常: {e}")
在类中抛出异常

在类的方法中也可以抛出异常。例如,当类的属性被赋予无效值时,可以抛出异常:

class Rectangle:
    def __init__(self, width, height):
        if width <= 0 or height <= 0:
            raise ValueError("宽度和高度必须为正数")
        self.width = width
        self.height = height
 
try:
    rect = Rectangle(-2, 3)
except ValueError as e:
    print(f"捕获到异常: {e}")

复杂案例:

案例一:信用卡支付
class PaymentError(Exception):
    """支付异常"""
    def __init__(self, amount, payment_method, error_code, *args):
        self.amount = amount
        self.payment_method = payment_method
        self.error_code = error_code
        self.details = args

        # 格式化错误消息
        details_str = f" - 详情: {', '.join(map(str, args))}" if args else ""
        super().__init__(
            f"支付失败: 金额 {amount} 元, 方式 {payment_method}, 错误码 {error_code}{details_str}"
        )


def process_payment(amount, payment_method):
    """处理支付"""
    if amount <= 0:
        raise PaymentError(
            amount,
            payment_method,
            "ERR001",
            "支付金额必须大于0"
        )

    if amount > 10000 and payment_method == "credit_card":
        raise PaymentError(
            amount,
            payment_method,
            "ERR002",
            "信用卡支付超过限额",
            "最大限额: 10000元",
            "请使用其他支付方式"
        )

    if payment_method not in ["credit_card", "alipay", "wechat_pay"]:
        raise PaymentError(
            amount,
            payment_method,
            "ERR003",
            "不支持的支付方式",
            f"支持的方式: credit_card, alipay, wechat_pay"
        )

    print(f"支付成功: {amount}元, 使用{payment_method}")
    return True


if __name__ == "__main__":
    """演示支付异常"""
    print("\n=== 支付系统演示 ===")

    payments = [
        (100, "credit_card"),
        (0, "alipay"),
        (15000, "credit_card"),
        (500, "bank_transfer"),
        (200, "alipay")
    ]

    for amount, method in payments:
        print(f"\n尝试支付: {amount}元, 使用{method}")

        try:
            process_payment(amount, method)
        except PaymentError as e:
            print(f"支付异常: {e}")
            print(f"错误码: {e.error_code}")
            print(f"详细信息: {e.details}")
        except Exception as e:
            print(f"其他错误: {e}")

以下是输出结果:

=== 支付系统演示 ===

尝试支付: 100元, 使用credit_card
支付成功: 100元, 使用credit_card

尝试支付: 0元, 使用alipay
支付异常: 支付失败: 金额 0 元, 方式 alipay, 错误码 ERR001 - 详情: 支付金额必须大于0
错误码: ERR001
详细信息: ('支付金额必须大于0',)

尝试支付: 15000元, 使用credit_card
支付异常: 支付失败: 金额 15000 元, 方式 credit_card, 错误码 ERR002 - 详情: 信用卡支付超过限额, 最大限额: 10000元, 请使用其他支付方式
错误码: ERR002
详细信息: ('信用卡支付超过限额', '最大限额: 10000元', '请使用其他支付方式')

尝试支付: 500元, 使用bank_transfer
支付异常: 支付失败: 金额 500 元, 方式 bank_transfer, 错误码 ERR003 - 详情: 不支持的支付方式, 支持的方式: credit_card, alipay, wechat_pay
错误码: ERR003
详细信息: ('不支持的支付方式', '支持的方式: credit_card, alipay, wechat_pay')

尝试支付: 200元, 使用alipay
支付成功: 200元, 使用alipay
案例二:用户校验
# 1. 定义自定义异常类
class InvalidUserError(Exception):
    """自定义异常:无效用户异常"""

    def __init__(self, username, age, email, *args):
        """
        初始化异常,接收多个参数
        Args:
            username: 用户名
            age: 年龄
            email: 邮箱
            *args: 其他信息
        """
        self.username = username
        self.age = age
        self.email = email
        self.additional_info = args

        # 创建错误消息
        message = f"用户 '{username}' 无效 - 年龄: {age}, 邮箱: {email}"
        if args:
            message += f", 其他信息: {', '.join(map(str, args))}"

        super().__init__(message)


# 2. 使用自定义异常的函数
def validate_user(username, age, email):
    """
    验证用户信息,如果不合法则抛出异常

    Args:
        username: 用户名
        age: 年龄
        email: 邮箱
    """
    errors = []

    # 验证用户名
    if not username or len(username) < 3:
        errors.append("用户名至少需要3个字符")

    # 验证年龄
    if age < 0 or age > 120:
        errors.append("年龄必须在0-120之间")

    # 验证邮箱
    if "@" not in email:
        errors.append("邮箱格式不正确")

    # 如果有错误,抛出自定义异常
    if errors:
        # 传递多个参数给异常
        raise InvalidUserError(
            username,  # 第一个参数
            age,  # 第二个参数
            email,  # 第三个参数
            f"验证失败: {errors}",  # 第四个参数
            len(errors)  # 第五个参数
        )

    print(f"用户 {username} 验证通过!")
    return True


# 3. 主程序 - 捕获和处理自定义异常
if __name__ == "__main__":
    # 测试用例
    test_cases = [
        ("ab", 25, "test@example.com"),  # 用户名太短
        ("john_doe", -5, "test@example.com"),  # 年龄无效
        ("alice", 30, "invalid-email"),  # 邮箱无效
        ("john_doe", 25, "test@example.com"),  # 有效用户
    ]

    for username, age, email in test_cases:
        print(f"\n正在验证用户: 用户名='{username}', 年龄={age}, 邮箱='{email}'")

        try:
            validate_user(username, age, email)

        except InvalidUserError as e:
            # 捕获自定义异常
            print(f"捕获到自定义异常: {e}")
            print(f"异常类型: {type(e).__name__}")
            print(f"用户名: {e.username}")
            print(f"年龄: {e.age}")
            print(f"邮箱: {e.email}")

            if e.additional_info:
                print(f"附加信息: {e.additional_info}")

            # 访问异常的所有属性
            print(f"所有异常属性: {e.__dict__}")

        except Exception as e:
            # 捕获其他异常
            print(f"捕获到其他异常: {type(e).__name__}: {e}")

以下是输出结果:

正在验证用户: 用户名='ab', 年龄=25, 邮箱='test@example.com'
捕获到自定义异常: 用户 'ab' 无效 - 年龄: 25, 邮箱: test@example.com, 其他信息: 验证失败: ['用户名至少需要3个字符'], 1
异常类型: InvalidUserError
用户名: ab
年龄: 25
邮箱: test@example.com
附加信息: ("验证失败: ['用户名至少需要3个字符']", 1)
所有异常属性: {'username': 'ab', 'age': 25, 'email': 'test@example.com', 'additional_info': ("验证失败: ['用户名至少需要3个字符']", 1)}

正在验证用户: 用户名='john_doe', 年龄=-5, 邮箱='test@example.com'
捕获到自定义异常: 用户 'john_doe' 无效 - 年龄: -5, 邮箱: test@example.com, 其他信息: 验证失败: ['年龄必须在0-120之间'], 1
异常类型: InvalidUserError
用户名: john_doe
年龄: -5
邮箱: test@example.com
附加信息: ("验证失败: ['年龄必须在0-120之间']", 1)
所有异常属性: {'username': 'john_doe', 'age': -5, 'email': 'test@example.com', 'additional_info': ("验证失败: ['年龄必须在0-120之间']", 1)}

正在验证用户: 用户名='alice', 年龄=30, 邮箱='invalid-email'
捕获到自定义异常: 用户 'alice' 无效 - 年龄: 30, 邮箱: invalid-email, 其他信息: 验证失败: ['邮箱格式不正确'], 1
异常类型: InvalidUserError
用户名: alice
年龄: 30
邮箱: invalid-email
附加信息: ("验证失败: ['邮箱格式不正确']", 1)
所有异常属性: {'username': 'alice', 'age': 30, 'email': 'invalid-email', 'additional_info': ("验证失败: ['邮箱格式不正确']", 1)}

正在验证用户: 用户名='john_doe', 年龄=25, 邮箱='test@example.com'
用户 john_doe 验证通过!
案例三:继承自定义异常
class NetworkError(Exception):
    """网络异常基类"""

    def __init__(self, url, status_code, message):
        self.url = url
        self.status_code = status_code
        self.message = message
        super().__init__(f"网络错误: {url} (状态码: {status_code}) - {message}")

class TimeoutError(NetworkError):
    """超时异常 - 继承自NetworkError"""

    def __init__(self, url, timeout_seconds):
        self.timeout_seconds = timeout_seconds
        super().__init__(url, 408, f"请求超时 ({timeout_seconds}秒)")

class NotFoundError(NetworkError):
    """资源未找到异常 - 继承自NetworkError"""

    def __init__(self, url):
        super().__init__(url, 404, "资源未找到")

def fetch_data(url):
    """模拟获取数据"""
    if "timeout" in url:
        raise TimeoutError(url, 30)
    elif "notfound" in url:
        raise NotFoundError(url)
    elif "servererror" in url:
        raise NetworkError(url, 500, "服务器内部错误")

    print(f"成功获取: {url}")
    return "data"


if __name__ == "__main__":
    """演示网络异常"""
    print("\n=== 网络请求演示 ===")

    urls = [
        "http://api.example.com/data",
        "http://api.example.com/timeout",
        "http://api.example.com/notfound",
        "http://api.example.com/servererror"
    ]

    for url in urls:
        print(f"\n请求URL: {url}")

        try:
            data = fetch_data(url)
        except TimeoutError as e:
            print(f"超时异常: {e}")
            print(f"超时时间: {e.timeout_seconds}秒")
        except NotFoundError as e:
            print(f"未找到异常: {e}")
            print(f"状态码: {e.status_code}")
        except NetworkError as e:
            print(f"网络异常: {e}")
            print(f"URL: {e.url}, 状态码: {e.status_code}")
        except Exception as e:
            print(f"其他异常: {e}")

以下是输出结果:

=== 网络请求演示 ===

请求URL: http://api.example.com/data
成功获取: http://api.example.com/data

请求URL: http://api.example.com/timeout
超时异常: 网络错误: http://api.example.com/timeout (状态码: 408) - 请求超时 (30)
超时时间: 30秒

请求URL: http://api.example.com/notfound
未找到异常: 网络错误: http://api.example.com/notfound (状态码: 404) - 资源未找到
状态码: 404

请求URL: http://api.example.com/servererror
网络异常: 网络错误: http://api.example.com/servererror (状态码: 500) - 服务器内部错误
URL: http://api.example.com/servererror, 状态码: 500

小结

本文详细介绍了 Python 中抛出异常的基础概念、使用方法、常见实践以及最佳实践。通过合理地抛出和处理异常,可以提高程序的健壮性和可维护性。在实际开发中,应根据具体情况选择合适的异常类型,并提供详细的异常信息。同时,要避免在不必要的地方抛出异常,以提高程序的性能。

六.全文概览

img
img
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.0.0-alpha.8