一、异常基础

  1. 定义:程序在编写或运行期间产生的错误。

    • 编写时的异常(如语法错误):必须解决。
    • 运行时的异常(多由数据导致):可处理也可不处理。
  2. 异常类体系

    • BaseException:所有异常的父类。
    • 常用子类:Exception(多数异常基于此类)。
  3. 常见异常类型

    • SyntaxError:语法错误(编写时)。
    • NameError:标识符错误(变量未定义等)。
    • TypeError:类型错误(运算时传入类型不匹配)。
    • KeyError:键错误(字典中键不存在)。
    • ValueError:值错误(值不符合预期,如int('a'))。
    • IndexError:索引错误(列表索引越界)。
    • ZeroDivisionError:除数为 0 错误。
    • ArithmeticError:算术错误(ZeroDivisionError的父类)。
    • StopIteration:迭代器异常(迭代结束后继续访问)。
    • RecursionError:递归异常(递归深度超限)。

二、异常传播机制

  • 当程序产生异常时,默认将异常抛给上层调用者,并终止当前程序。
  • 若上层未处理,继续向上传播,直至 Python 解析器:解析器输出异常栈(先进后出)并强制终止程序。
  • 若上层处理了异常,则异常不再传播,程序可继续执行。

示例

def a():
    print("-----a-----")
    b()  # 调用b,若b出错则抛给a
    print("-----a结束-----")

def b():
    print("-----b-----")
    print(3/0)  # 产生ZeroDivisionError
    print("-----b结束-----")

a()  # 异常从b传到a,再传到解析器,程序终止

三、异常处理机制(try-except)

用于捕获并处理可能产生异常的代码,避免程序终止。

基本语法:

try:
    # 可能产生异常的代码
except 异常类型1 as e:
    # 处理异常类型1的逻辑(e为异常对象,可获取原因)
except 异常类型2 as e:
    # 处理异常类型2的逻辑
...
扩展用法:
  1. 捕获多种异常(同处理逻辑):用元组包含多个异常类型。

    try:
        a = int(input("输入数字:"))
        b = int(input("输入数字:"))
        print(a/b)
    except (ValueError, ZeroDivisionError) as e:
        print(f"异常已处理:{e}")  # 同时处理值错误和除0错误
    
  2. 捕获所有异常

    • Exception(推荐,覆盖多数异常)或BaseException
    • 若省略异常类型,可处理所有异常,但无法直接获取异常原因(需通过sys.exception()traceback)。
  3. 保留异常栈信息:使用traceback.print_exc()打印详细异常栈。

    from traceback import print_exc
    
    try:
        1/0
    except:
        print_exc()  # 输出完整异常栈
    

四、try-except-else

  • else块:当try中代码无异常时执行。

示例

try:
    a = int(input("数字1:"))
    b = int(input("数字2:"))
    print(a/b)
except (ValueError, ZeroDivisionError) as e:
    print(f"异常:{e}")
else:
    print("程序执行完毕(无异常)")  # 无异常时执行

五、try-except-finally

  • finally块:无论try中是否产生异常,一定会执行(前提是try代码被执行)。
  • 用途:释放资源(如关闭文件、数据库连接)。

示例

f = None
try:
    f = open("file.txt")
    f.read()
except:
    print("读取错误")
finally:
    if f:  # 确保文件关闭
        f.close()

  • 特殊情况:finally中修改返回值的影响(引用类型会生效,值类型不生效):

    def test1():
        a = [10]
        try:
            return a
        finally:
            a[0] += 10  # 列表是引用类型,返回值会变为[20]
    
    def test2():
        a = 10
        try:
            return a
        finally:
            a += 10  # 整数是值类型,返回值仍为10
    

六、自定义异常类

  • 用途:包装官方异常或定义业务特定异常。
  • 实现:继承已知异常类(推荐Exception)。

示例

class ServiceError(Exception):
    """业务异常类"""
    pass

class QikuException(Exception):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)  # 调用父类构造

# 使用:捕获官方异常后抛出自定义异常
try:
    3/0
except ZeroDivisionError as e:
    raise ServiceError(f"业务错误:{e}")  # 抛出ServiceError

七、断言(assert)

  • 语法:assert 条件, 消息
  • 逻辑:若条件为True,程序继续;若为False,抛出AssertionError,异常信息为 “消息”。

示例

b = 10 / 3
assert isinstance(b, int), "b必须是整数"  # 条件为False,抛出AssertionError

八、日志模块(logging)

  • 作用:记录系统运行信息,用于追踪和排查问题。
  • 可根据环境配置日志级别(开发环境多日志,生产环境少日志)。
1. 日志级别(从低到高)
  • DEBUG:调试信息(级别最低,输出最多,包含所有级别)。
  • INFO:通知信息(业务流程日志,输出INFO及以上级别)。
  • WARNING:警告信息(输出WARNING及以上级别)。
  • ERROR:错误信息(级别最高,仅输出ERROR)。
2. 输出目的地
  • 控制台(console)、文件(file)、邮件(email)、数据库(db)等。
3. 输出格式参数
  • %(levelname)s:日志级别。
  • %(asctime)s:日志产生时间。
  • %(pathname)s:调用日志的源文件完整路径。
  • %(filename)s:调用日志的源文件名。
  • %(lineno)d:调用日志的源代码行号。
  • %(message)s:日志消息内容。

 

一、异常处理练习题

1. 数字转换异常处理

题目:编写程序,要求用户输入一个数字并转换为整数。若输入非数字,捕获ValueError并打印错误消息。

解答

try:
    a = int(input("输入一个数字:"))
except ValueError as e:
    print("输入的不是数字", str(e))
2. 列表索引异常处理

题目:创建含数字的列表,尝试访问索引为 10 的元素。捕获IndexError并打印适当消息。

解答

try:
    ls = [1, 2, 3, 4, 5, 6, 7]
    print(ls[10])
except IndexError as e:
    print('未找到该索引位置对应的值', str(e))
3. 除法除零异常处理

题目:要求用户输入两个数字并相除。捕获ZeroDivisionError,若发生则提醒 “不能除以零”。

解答

from traceback import print_exc

try:
    a = int(input('请输入被除数:'))
    b = int(input('请输入除数:'))
    c = a / b
except ZeroDivisionError as e:
    print_exc()  # 打印异常栈
    print('除数不能为0')
4. 列表元素转整数异常处理

题目:创建含字符串的列表,尝试将每个元素转为整数。捕获ValueError并打印错误消息。

解答

from traceback import print_exc

try:
    ls = [1, 2, 'string', 2, 3, '122']
    ls = [int(x) for x in ls]  # 列表推导式转换
except ValueError as e:
    print_exc()  # 打印异常栈
    print('存在不能转化成整数的类型!')
5. 多异常(索引 + 键)处理

题目:创建含字典的列表,尝试访问索引为 5 的元素的键 "name"。捕获IndexErrorKeyError,分别打印消息。

解答

from traceback import print_exc

try:
    ls = [1, 2, 3, 4, 5, 6, {'age': 12}]
    print(ls[10])  # 可能触发IndexError
    print(ls[5]['name'])  # 可能触发KeyError
except (IndexError, KeyError) as e:
    print(f"发生错误: {type(e)}")
    print(f"错误详情: {e}")
6. 索引类型异常处理

题目:创建含数字的列表,尝试访问索引为 "abc" 的元素。捕获TypeError并打印消息。

解答

try:
    ls = [1, 2, 3, 4, 5, 6, 'abcd']
    print(ls['abc'])  # 字符串索引非法(列表索引需为整数)
except TypeError as e:
    print('索引格式错误', str(e))
7. 平方根计算异常处理

题目:要求用户输入数字并计算平方根。捕获ValueError(非数字输入)和ArithmeticError(负数开方),分别打印消息。

解答

try:
    a = int(input("请输入一个数字:"))
    if a < 0:
        raise ArithmeticError("负数不能开方")  # 主动抛出算术异常
    print(a ** 0.5)
except ValueError as e:
    print(f"输入的不是一个数字, {e}")
except ArithmeticError as e:
    print(f"算数异常,{e}")
8. 倒数计算异常处理

题目:创建含整数的列表,尝试计算每个元素的倒数。捕获ZeroDivisionError并提醒 “不能除以零”。

解答

from traceback import print_exc

try:
    ls = [0, 100, 200, 300, 500, 10, 20, 1]
    ls = [1 / x for x in ls]  # 列表推导式计算倒数
except ZeroDivisionError:
    print_exc()  # 打印异常栈
    print('不可以除以0!!')
9. 带重试机制的网页内容获取

题目:编写open_url函数,接收网址并获取文本内容(用requests库)。要求:读取时若产生错误,最多重试 3 次。

解答

import requests

def open_url(url: str, headers: dict, retry: int = 3):
    """获取指定网址返回的文本内容,最多重试retry次"""
    i = 0
    while i < retry:
        try:
            response = requests.get(url, headers=headers)
            text = response.text
            print(text)  # 打印网页内容
            return text
        except:
            i += 1
            print(f'第{i}次出现错误')  # 记录重试次数
    return None  # 重试耗尽仍失败,返回None

if __name__ == '__main__':
    url = "https://www.example.com"
    headers = {"User-Agent": "Mozilla/5.0"}  # 模拟浏览器请求头
    result = open_url(url, headers)

二、令牌生成与验证问题

题目要求
  1. 生成令牌函数generator_remember_token(username, password, key, duration)

    • 令牌规则:base64(<username>:<timestamp>:md5(<username>:<timestamp>:<key>:<password>))
    • 其中timestamp为令牌过期时间戳(当前时间 + 有效时长duration,精确到秒)。
  2. 验证令牌函数check_remember_token(token, username, password, key)

    • 验证规则:
      1. 令牌中的用户名必须与传入的username一致;
      2. 验证令牌中的 MD5 值,确保密码匹配;
      3. 检查时间戳是否过期(当前时间≤过期时间戳)。
解答代码

import base64
import hashlib
import time

def generator_remember_token(username, password, key, duration):
    """生成令牌:按规则编码用户名、过期时间戳和加密信息"""
    now = int(time.time())  # 当前时间戳(秒)
    timestamp = now + duration  # 过期时间戳 = 当前时间 + 有效时长
    # 生成MD5加密串:拼接用户名、时间戳、密钥、密码后加密
    md5_str = hashlib.md5(f"{username}:{timestamp}:{key}:{password}".encode()).hexdigest()
    # 拼接用户名、时间戳、MD5串,再Base64编码
    token_str = f"{username}:{timestamp}:{md5_str}"
    token = base64.b64encode(token_str.encode())  # Base64编码(字节类型)
    return token

def check_remember_token(token, username, password, key):
    """验证令牌:检查用户名、密码匹配性和时效性"""
    # 解码令牌:Base64解码后转字符串
    token_decoded = base64.b64decode(token).decode()
    # 拆分令牌为:用户名、时间戳、MD5串
    parts = token_decoded.split(':')
    if len(parts) != 3:
        raise Exception("令牌格式错误")  # 格式异常处理
    
    token_username, token_timestamp, token_md5 = parts
    current_time = int(time.time())  # 当前时间戳
    
    # 1. 验证用户名一致
    if token_username != username:
        raise Exception('用户名错误!')
    
    # 2. 验证时间戳未过期
    if current_time > int(token_timestamp):
        raise Exception('令牌已过期!')
    
    # 3. 验证MD5值(密码和密钥匹配)
    expected_md5 = hashlib.md5(f"{username}:{token_timestamp}:{key}:{password}".encode()).hexdigest()
    if expected_md5 != token_md5:
        raise Exception("密码或密钥不匹配")
    
    return True  # 所有验证通过

if __name__ == "__main__":
    # 示例测试
    username = "user1"
    password = "password123"
    key = "secretkey"
    duration = 5  # 令牌有效时长5秒
    
    # 生成令牌
    token = generator_remember_token(username, password, key, duration)
    print(f"生成的令牌: {token}")
    
    # 等待10秒(超过有效时长,测试过期逻辑)
    time.sleep(10)
    
    # 验证令牌
    try:
        is_valid = check_remember_token(token, username, password, key)
        print(f"令牌有效: {is_valid}")
    except Exception as e:
        print(f"验证失败: {e}")  # 预期输出"验证失败: 令牌已过期!"

 

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐