Skip to content

Python 错误和异常

在 Python 编程中,错误和异常是不可避免的。本章节将详细介绍 Python 中的错误和异常类型,以及如何有效地处理它们。

错误与异常的区别

  • 错误(Error):通常是指程序中的语法错误或逻辑错误,导致程序无法正常执行。
  • 异常(Exception):是指程序运行过程中发生的事件,如除以零、文件不存在等,会中断程序的正常执行。

异常类型

Python 内置了许多异常类型,以下是一些常见的异常类型:

异常类型描述
SyntaxError语法错误
IndentationError缩进错误
NameError变量名不存在
TypeError类型错误
ValueError值错误
ZeroDivisionError除以零错误
FileNotFoundError文件不存在错误
IOErrorI/O 错误
ImportError导入错误
KeyError字典键不存在错误
IndexError索引错误
AttributeError属性错误
OverflowError溢出错误
RecursionError递归错误
PermissionError权限错误

异常处理

Python 使用 try-except 语句来处理异常。

基本语法

python
try:
    # 可能会引发异常的代码
    pass
except [异常类型 [as 变量名]]:
    # 异常处理代码
    pass
[else:
    # 没有异常时执行的代码
    pass]
[finally:
    # 无论是否有异常都会执行的代码
    pass]

示例 1:基本异常处理

python
# 基本异常处理
try:
    x = 10 / 0
except ZeroDivisionError:
    print("错误:除以零")
print("程序继续执行")

输出:

错误:除以零
程序继续执行

示例 2:捕获多个异常

python
# 捕获多个异常
try:
    x = int(input("请输入一个数字:"))
    y = 10 / x
    print(f"10 / {x} = {y}")
except ZeroDivisionError:
    print("错误:除以零")
except ValueError:
    print("错误:请输入有效的数字")
print("程序继续执行")

输出:

请输入一个数字:0
错误:除以零
程序继续执行

请输入一个数字:abc
错误:请输入有效的数字
程序继续执行

示例 3:捕获所有异常

python
# 捕获所有异常
try:
    x = 10 / 0
except Exception as e:
    print(f"发生错误:{e}")
print("程序继续执行")

输出:

发生错误:division by zero
程序继续执行

示例 4:else 子句

python
# else 子句
try:
    x = int(input("请输入一个数字:"))
    y = 10 / x
except ZeroDivisionError:
    print("错误:除以零")
except ValueError:
    print("错误:请输入有效的数字")
else:
    print(f"计算成功:10 / {x} = {y}")
print("程序继续执行")

输出:

请输入一个数字:2
计算成功:10 / 2 = 5.0
程序继续执行

示例 5:finally 子句

python
# finally 子句
try:
    f = open("example.txt", "r")
    content = f.read()
    print(content)
except FileNotFoundError:
    print("错误:文件不存在")
finally:
    if 'f' in locals() and not f.closed:
        f.close()
        print("文件已关闭")
print("程序继续执行")

输出:

如果文件不存在:

错误:文件不存在
程序继续执行

如果文件存在:

文件内容
文件已关闭
程序继续执行

抛出异常

使用 raise 语句可以主动抛出异常。

基本语法

python
raise [异常类型 [, 参数]]

示例 1:抛出内置异常

python
# 抛出内置异常
def divide(a, b):
    if b == 0:
        raise ZeroDivisionError("除数不能为零")
    return a / b

try:
    result = divide(10, 0)
except ZeroDivisionError as e:
    print(f"错误:{e}")

输出:

错误:除数不能为零

示例 2:抛出自定义异常

python
# 抛出自定义异常
class CustomError(Exception):
    """自定义异常类"""
    pass

def check_age(age):
    if age < 0:
        raise CustomError("年龄不能为负数")
    elif age > 120:
        raise CustomError("年龄不能超过 120")
    return age

try:
    age = check_age(-5)
except CustomError as e:
    print(f"错误:{e}")

try:
    age = check_age(150)
except CustomError as e:
    print(f"错误:{e}")

输出:

错误:年龄不能为负数
错误:年龄不能超过 120

自定义异常类

可以通过继承 Exception 类来创建自定义异常类。

示例

python
# 自定义异常类
class ValidationError(Exception):
    """验证错误异常"""
    def __init__(self, message, field=None):
        self.message = message
        self.field = field
        super().__init__(self.message)
    
    def __str__(self):
        if self.field:
            return f"ValidationError: {self.message} (字段: {self.field})"
        return f"ValidationError: {self.message}"

class DatabaseError(Exception):
    """数据库错误异常"""
    pass

# 使用自定义异常
def validate_user(user_data):
    if not user_data.get('name'):
        raise ValidationError('姓名不能为空', 'name')
    if not user_data.get('email'):
        raise ValidationError('邮箱不能为空', 'email')
    if '@' not in user_data.get('email', ''):
        raise ValidationError('邮箱格式不正确', 'email')
    return True

try:
    user = {
        'name': 'Alice',
        'email': 'alice@example.com'
    }
    validate_user(user)
    print("用户验证成功")
except ValidationError as e:
    print(f"验证错误:{e}")

try:
    user = {
        'name': 'Bob',
        'email': 'bob.example.com'  # 错误的邮箱格式
    }
    validate_user(user)
    print("用户验证成功")
except ValidationError as e:
    print(f"验证错误:{e}")

输出:

用户验证成功
验证错误:ValidationError: 邮箱格式不正确 (字段: email)

异常链

在 Python 3 中,可以使用 raise ... from ... 语句来创建异常链,保留原始异常信息。

示例

python
# 异常链
try:
    x = 10 / 0
except ZeroDivisionError as e:
    raise ValueError("计算错误") from e

输出:

Traceback (most recent call last):
  File "example.py", line 3, in <module>
    x = 10 / 0
ZeroDivisionError: division by zero

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "example.py", line 5, in <module>
    raise ValueError("计算错误") from e
ValueError: 计算错误

异常处理的最佳实践

1. 只捕获必要的异常

python
# 不好的做法:捕获所有异常
try:
    x = 10 / 0
except:
    print("发生错误")

# 好的做法:只捕获特定异常
try:
    x = 10 / 0
except ZeroDivisionError:
    print("错误:除以零")

2. 使用具体的异常类型

python
# 不好的做法:使用过于宽泛的异常
try:
    x = int(input("请输入一个数字:"))
except Exception as e:
    print(f"错误:{e}")

# 好的做法:使用具体的异常类型
try:
    x = int(input("请输入一个数字:"))
except ValueError:
    print("错误:请输入有效的数字")

3. 提供有意义的错误信息

python
# 不好的做法:错误信息不明确
try:
    with open("nonexistent.txt", "r") as f:
        content = f.read()
except FileNotFoundError:
    print("错误:文件不存在")

# 好的做法:提供具体的错误信息
try:
    filename = "nonexistent.txt"
    with open(filename, "r") as f:
        content = f.read()
except FileNotFoundError:
    print(f"错误:文件 '{filename}' 不存在")

4. 使用 finally 子句清理资源

python
# 不好的做法:没有清理资源
f = open("example.txt", "r")
try:
    content = f.read()
    print(content)
except FileNotFoundError:
    print("错误:文件不存在")
# 如果发生异常,文件可能不会被关闭
f.close()

# 好的做法:使用 finally 子句
f = None
try:
    f = open("example.txt", "r")
    content = f.read()
    print(content)
except FileNotFoundError:
    print("错误:文件不存在")
finally:
    if f and not f.closed:
        f.close()
        print("文件已关闭")

# 更好的做法:使用 with 语句
with open("example.txt", "r") as f:
    content = f.read()
    print(content)
# with 语句会自动关闭文件

5. 避免在 except 块中使用 pass

python
# 不好的做法:使用 pass 忽略异常
try:
    x = 10 / 0
except ZeroDivisionError:
    pass  # 忽略异常

# 好的做法:至少记录异常
try:
    x = 10 / 0
except ZeroDivisionError:
    print("警告:除以零,结果设置为 0")
    x = 0

6. 合理使用 else 子句

python
# 好的做法:使用 else 子句分离正常逻辑和异常处理
try:
    x = int(input("请输入一个数字:"))
except ValueError:
    print("错误:请输入有效的数字")
else:
    print(f"你输入的数字是:{x}")
    # 其他正常逻辑...

实际应用示例

示例 1:文件操作异常处理

python
# 文件操作异常处理
def read_file(filename):
    """读取文件内容"""
    try:
        with open(filename, 'r', encoding='utf-8') as f:
            content = f.read()
        return content
    except FileNotFoundError:
        print(f"错误:文件 '{filename}' 不存在")
        return None
    except UnicodeDecodeError:
        print(f"错误:文件 '{filename}' 编码错误")
        return None
    except Exception as e:
        print(f"错误:读取文件时发生未知错误:{e}")
        return None

# 测试
content = read_file("example.txt")
if content:
    print(f"文件内容:{content}")
else:
    print("无法读取文件")

示例 2:网络请求异常处理

python
# 网络请求异常处理
import requests

def get_data(url):
    """获取网络数据"""
    try:
        response = requests.get(url, timeout=5)
        response.raise_for_status()  # 检查 HTTP 状态码
        return response.json()
    except requests.exceptions.Timeout:
        print("错误:请求超时")
        return None
    except requests.exceptions.HTTPError as e:
        print(f"错误:HTTP 错误:{e}")
        return None
    except requests.exceptions.ConnectionError:
        print("错误:连接错误")
        return None
    except Exception as e:
        print(f"错误:发生未知错误:{e}")
        return None

# 测试
# data = get_data("https://api.example.com/data")
# if data:
#     print(f"获取的数据:{data}")
# else:
#     print("无法获取数据")

示例 3:数据库操作异常处理

python
# 数据库操作异常处理
import sqlite3

def query_database(db_path, query, params=()):
    """查询数据库"""
    conn = None
    try:
        conn = sqlite3.connect(db_path)
        cursor = conn.cursor()
        cursor.execute(query, params)
        result = cursor.fetchall()
        conn.commit()
        return result
    except sqlite3.Error as e:
        print(f"错误:数据库操作失败:{e}")
        if conn:
            conn.rollback()
        return None
    finally:
        if conn:
            conn.close()

# 测试
# result = query_database("example.db", "SELECT * FROM users WHERE age > ?", (18,))
# if result:
#     print(f"查询结果:{result}")
# else:
#     print("查询失败")

示例 4:用户输入验证

python
# 用户输入验证
def get_valid_integer(prompt, min_value=None, max_value=None):
    """获取有效的整数输入"""
    while True:
        try:
            value = int(input(prompt))
            if min_value is not None and value < min_value:
                print(f"错误:值不能小于 {min_value}")
                continue
            if max_value is not None and value > max_value:
                print(f"错误:值不能大于 {max_value}")
                continue
            return value
        except ValueError:
            print("错误:请输入有效的整数")
            continue

# 测试
age = get_valid_integer("请输入你的年龄:", 0, 120)
print(f"你的年龄是:{age}")

score = get_valid_integer("请输入你的分数:", 0, 100)
print(f"你的分数是:{score}")

示例 5:自定义异常处理装饰器

python
# 自定义异常处理装饰器
def exception_handler(func):
    """异常处理装饰器"""
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            print(f"错误:{func.__name__} 函数执行失败:{e}")
            return None
    return wrapper

@exception_handler
def divide(a, b):
    return a / b

@exception_handler
def get_user(user_id):
    users = {1: "Alice", 2: "Bob", 3: "Charlie"}
    return users[user_id]

# 测试
result = divide(10, 2)
print(f"10 / 2 = {result}")

result = divide(10, 0)
print(f"10 / 0 = {result}")

result = get_user(1)
print(f"用户 1:{result}")

result = get_user(4)  # 不存在的用户 ID
print(f"用户 4:{result}")

调试技巧

1. 使用 print 语句

python
# 使用 print 语句调试
def add(a, b):
    print(f"调试:a = {a}, b = {b}")
    result = a + b
    print(f"调试:result = {result}")
    return result

add(1, 2)

2. 使用 assert 语句

python
# 使用 assert 语句调试
def divide(a, b):
    assert b != 0, "除数不能为零"
    return a / b

divide(10, 2)
divide(10, 0)  # 会引发 AssertionError

3. 使用 logging 模块

python
# 使用 logging 模块调试
import logging

# 配置 logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')

def add(a, b):
    logging.debug(f"a = {a}, b = {b}")
    result = a + b
    logging.debug(f"result = {result}")
    return result

add(1, 2)

4. 使用 pdb 调试器

python
# 使用 pdb 调试器
import pdb

def add(a, b):
    pdb.set_trace()  # 设置断点
    result = a + b
    return result

add(1, 2)

总结

Python 中的错误和异常处理是编写健壮程序的重要组成部分。本章节介绍了:

  1. 错误与异常的区别:错误是语法或逻辑错误,异常是运行时事件。
  2. 常见异常类型:如 SyntaxError、TypeError、ValueError 等。
  3. 异常处理语句:try-except-else-finally。
  4. 抛出异常:使用 raise 语句。
  5. 自定义异常:继承 Exception 类。
  6. 异常链:使用 raise ... from ... 语句。
  7. 异常处理的最佳实践:只捕获必要的异常、提供有意义的错误信息等。
  8. 实际应用示例:文件操作、网络请求、数据库操作等。
  9. 调试技巧:使用 print、assert、logging 和 pdb。

通过合理的异常处理,可以使程序更加健壮,提高用户体验,并便于调试和维护。