当前位置: 首页 > news >正文

【Python】错误和异常

目录

  • 错误和异常
    • 语法错误
    • 异常处理
    • try-except 块
    • try-finally 块
    • 抛出异常
    • 异常链
    • 嵌套 try 块
    • 自定义异常
    • 日志
    • 断言
    • 警告
    • 内置异常

错误和异常

语法错误

什么是语法错误?

在 Python 中,语法错误(Syntax Error) 是最常见的一类错误。
它发生在你编写的代码 不符合 Python 语法规则 时。

  • 检测阶段:语法错误会在 代码解析阶段(parsing) 被解释器发现,程序根本不会开始执行。
  • 本质原因:你的代码不符合 Python 的“语法文法”,导致解释器无法理解你的指令。

举例:

if Trueprint("Hello")

运行会报错:

  File "test.py", line 1if True^
SyntaxError: expected ':'

解释器告诉我们:if 后面缺少冒号。

常见的语法错误原因

  1. 缺少冒号 (😃

    ifforwhiledefclass 等语句后必须加冒号,否则报错。

    # ❌ 错误
    if Trueprint("Yes")
    

    报错:

    SyntaxError: expected ':'
    

    修复:

    # ✅ 正确
    if True:print("Yes")
    
  2. 缩进错误(Indentation Error)

    Python 依靠 缩进 来表示代码块。缩进不正确会报语法错误。

    # ❌ 错误
    def test():
    print("Hello")
    

    报错:

    IndentationError: expected an indented block
    

    修复:

    # ✅ 正确
    def test():print("Hello")
    
  3. 拼写错误或关键字使用错误

    # ❌ 错误
    prnt("Hello")
    

    报错:

    NameError: name 'prnt' is not defined
    

    虽然这不是严格的 SyntaxError,但属于常见的新手错误。
    正确写法:

    print("Hello")
    
  4. 括号、方括号、大括号不匹配

    Python 要求括号必须成对出现。

    # ❌ 错误
    print("Hello"
    

    报错:

    SyntaxError: '(' was never closed
    

    修复:

    print("Hello")
    

如何定位语法错误?

  1. 阅读错误信息

    Python 的错误信息非常直观。一般包含:

    • 文件名
    • 行号
    • 出错的代码片段
    • 错误类型(SyntaxError)

    示例:

    File "script.py", line 1print("Hello, World!"^
    SyntaxError: EOL while scanning string literal
    

    解释:

    • File “script.py” → 出错的文件
    • line 1 → 出错行号
    • ^ → 指示大概的出错位置
    • EOL while scanning string literal → 字符串没有正确结束(缺少引号或括号)
  2. IDE 帮助

    使用 IDE(集成开发环境)能在写代码时及时发现语法错误。常见特性:

    • 语法高亮:不同语法元素显示不同颜色,异常着色可能提示错误。
    • 错误下划线:错误位置通常有红色波浪线。
    • Lint 工具(如 pylintflake8):自动检测语法问题。

    常用 IDE:PyCharm、VS Code、Jupyter Notebook。

  3. 分块运行代码

    如果脚本很大,可以 逐步运行小片段,快速定位错误位置。
    例如一个文件有多个函数,可以单独运行某个函数来排查。

  4. 使用版本控制:如果你用 Git 管理代码,可以比较版本差异,找到最近引入语法错误的修改。

修复语法错误的方法

修复步骤:

  1. 仔细阅读错误信息
    Python 会告诉你文件名、行号和错误原因。
  2. 定位代码位置
    转到报错行,有时错误在上一行(比如缺冒号导致下一行报错)。
  3. 理解错误类型
    常见的有:
    • expected ':' → 缺冒号
    • EOL while scanning string literal → 字符串没有闭合
    • unexpected indent → 缩进错误
  4. 修改代码
    根据提示修复,再次运行。

异常处理

什么是异常(Exception)?

在 Python 中,异常就是程序运行过程中出现的错误,它会打断程序的正常执行流程。
语法错误(Syntax Error) 不同,语法错误在程序运行前就会被检测出来,而 异常是在程序运行时 触发的。

例如:

print(10 / 0)  # 除以零

运行结果:

ZeroDivisionError: division by zero

这里 ZeroDivisionError 就是异常。

总结:

  • 语法错误(Syntax Error):解释器无法理解代码,程序不能运行。
  • 运行时异常(Runtime Exception):程序能运行,但遇到非法操作时会抛出异常。

为什么要处理异常?

如果程序中出现异常但未处理,Python 会直接报错并终止程序。
为了让程序更加健壮,我们需要使用 异常处理机制 来捕获错误,给出合理的应对方式,而不是让程序崩溃。

异常处理的语法

Python 用 try…except…else…finally 语句来进行异常处理。

  1. 基本用法:try-except

    try:# 可能出错的代码result = 10 / 0
    except ZeroDivisionError:# 出错后执行的代码print("不能除以零")
    

    输出:

    不能除以零
    

    解释:

    • try:放置可能出错的代码。
    • except:指定要捕获的异常类型,并写上出错后的处理方式。
  2. 多个 except

    try:x = int("abc")  # 会触发 ValueError
    except ZeroDivisionError:print("除以零错误")
    except ValueError:print("数值转换错误")
    

    输出:

    数值转换错误
    
  3. 捕获所有异常

    try:x = 10 / 0
    except Exception as e:print("出错啦:", e)
    

    输出:

    出错啦: division by zero
    

    注意:except Exception 会捕获所有标准异常,但不推荐总是用它,因为这样可能掩盖真正的错误。

  4. else 子句

    else 代码块在 try 中没有异常时执行

    try:num = int("123")
    except ValueError:print("数值转换错误")
    else:print("转换成功:", num)
    

    输出:

    转换成功: 123
    
  5. finally 子句

    无论是否发生异常,finally 中的代码 都会执行
    通常用于 资源释放(如关闭文件、断开数据库连接)。

    try:f = open("test.txt", "w")f.write("Hello")
    except IOError:print("文件错误")
    finally:print("关闭文件")f.close()
    

    即使写入失败,finally 里的 f.close() 也会执行。

异常对象与参数

异常类的对象可以带有参数,提供错误信息。

try:num = int("xyz")
except ValueError as e:print("捕获到异常:", e)

输出:

捕获到异常: invalid literal for int() with base 10: 'xyz'

这里 e 就是异常对象,里面包含错误原因。

抛出异常(raise)

有时候我们希望 主动抛出异常 来提示错误。

def divide(a, b):if b == 0:raise ZeroDivisionError("分母不能为零")return a / btry:print(divide(10, 0))
except ZeroDivisionError as e:print("错误:", e)

输出:

错误: 分母不能为零

自定义异常

Python 允许我们创建自己的异常类(必须继承 Exception)。

class MyError(Exception):def __init__(self, message):self.message = messagetry:raise MyError("这是自定义错误")
except MyError as e:print("捕获自定义异常:", e.message)

输出:

捕获自定义异常: 这是自定义错误

断言(Assertions)

断言是异常处理的一种简化形式,用于 检查条件是否满足
如果条件不满足,Python 会抛出 AssertionError

def KelvinToFahrenheit(temp):assert temp >= 0, "温度不能低于绝对零度"return (temp - 273) * 1.8 + 32print(KelvinToFahrenheit(273))   # 32.0
print(KelvinToFahrenheit(-5))    # AssertionError

输出:

32.0
AssertionError: 温度不能低于绝对零度

标准异常表(常见)

异常类型触发场景
ZeroDivisionError除以零
ValueError无效的数值转换
TypeError操作类型错误(如字符串+整数)
NameError使用了未定义的变量
IndexError列表下标越界
KeyError字典中不存在的键
IOError / OSError文件或操作系统相关错误
ImportError导入模块失败
AttributeError访问对象不存在的属性
AssertionError断言失败

try-except 块

什么是 try-except

在 Python 中,异常(Exception) 是指程序运行过程中发生的错误。
如果不处理异常,程序会 直接中断 并报错。

异常处理的目的:让程序在遇到错误时,不是直接崩溃,而是通过 try-except 优雅地处理,继续执行。

例如:

print(10 / 0)   # ZeroDivisionError: division by zero

程序会崩溃,后续代码不会执行。
但如果我们用 try-except

try:print(10 / 0)
except ZeroDivisionError:print("错误:不能除以 0")
print("程序继续运行")

输出:

错误:不能除以 0
程序继续运行

基本语法

try:# 可能出错的代码risky_code()
except SomeException as e:# 出错后执行的代码handle_exception(e)
  • try:放置可能出错的代码。
  • except:捕获错误并处理。
  • SomeException:指定要捕获的异常类型(比如 ValueError, ZeroDivisionError 等)。
  • as e:把异常对象存到变量 e 中,方便后续查看错误信息。

单一异常处理

try:number = int(input("请输入一个数字:"))result = 10 / numberprint("结果:", result)
except ZeroDivisionError:print("错误:不能除以 0")

多个异常处理

try:number = int(input("请输入一个数字:"))result = 10 / numberprint("结果:", result)
except ZeroDivisionError:print("错误:不能除以 0")
except ValueError:print("错误:请输入合法的数字")

同时捕获多个异常

如果不同异常的处理方式相同,可以写在一起:

try:x = int(input("输入一个数字:"))print(10 / x)
except (ZeroDivisionError, ValueError):print("输入错误,要么是 0,要么不是数字")

使用 else

作用else 中的代码只有在 没有发生异常 时才会执行。

try:x = int(input("请输入一个整数:"))y = 10 / x
except ZeroDivisionError:print("错误:除数不能为 0")
except ValueError:print("错误:请输入整数")
else:print("计算成功,结果是:", y)

如果输入 5,结果:

计算成功,结果是: 2.0

使用 finally

  • 作用:无论是否发生异常,finally 中的代码 都会执行
  • 常用于 清理资源:关闭文件、关闭数据库连接、释放锁等。
try:file = open("example.txt", "r")content = file.read()print(content)
except FileNotFoundError:print("错误:文件未找到")
else:print("文件读取成功")
finally:print("关闭文件...")if 'file' in locals():file.close()

即使文件不存在,也会执行 finally 中的关闭操作。

工作原理

  • try-except 工作流程

    1. 执行 try 块里的代码。
    2. 如果没有异常 → 跳过 except,执行 else(如果有)。
    3. 如果发生异常 → 进入匹配的 except,然后跳过 else
    4. 最后一定会执行 finally(如果有)。
  • 异常的传播
    如果 try 块中没有捕获异常,Python 会向调用者一层层“抛出”,直到最顶层。如果仍然没捕获,程序就会终止。

注意事项

  1. 捕获具体异常,而不是用裸 except:
    ❌ 不推荐:

    try:x = 10 / 0
    except:print("出错了")
    

    这样会把所有异常(甚至 KeyboardInterrupt)都捕获掉,不利于排查问题。

    ✅ 推荐:

    try:x = 10 / 0
    except ZeroDivisionError:print("不能除以 0")
    
  2. as e 打印异常信息

    try:int("abc")
    except ValueError as e:print("错误信息:", e)
    

    输出:

    错误信息: invalid literal for int() with base 10: 'abc'
    
  3. 合理使用 else 和 finally

    • else:放置“仅在没出错时才运行”的逻辑。
    • finally:放置“无论如何都要运行”的清理代码。

try-finally 块

什么是 try-finally

在 Python 中,try-finally 是异常处理的一种形式。它和 try-except 的区别是:

  • try-except:用于捕获并处理异常,保证程序不会因为错误崩溃。
  • try-finally:不捕获异常,而是保证 无论是否发生异常,finally 块中的代码都会执行。通常用于 资源清理必须执行的关键操作(例如关闭文件、释放锁、网络连接关闭等)。

换句话说:finally 用来确保 “必做操作”

基本语法

try:# 可能会出错的代码risky_code()
finally:# 无论如何都会执行的代码cleanup_code()
  • try 块:放置可能抛出异常的代码。
  • finally 块:放置清理或收尾代码,无论 try 块是否抛出异常,这里的代码都会执行。

注意
try-finally 本身不处理异常,它只是保证 finally 的执行。如果需要处理异常,可以把 try-finally 放在 try-except 内层,或者在 finally 外层加上 try-except

示例:打开文件写入内容,并保证文件关闭

try:fh = open("testfile", "w")fh.write("这是一个测试文件,用于演示 try-finally!")
finally:print("无论如何都会执行这句话")fh.close()

解释:

  1. 打开文件并写入内容。
  2. 无论写入成功还是失败,finally 都会执行,文件会被关闭。
  3. 这保证了资源不会泄漏。

嵌套使用 try-finallytry-except

为了同时保证资源清理并捕获异常,可以嵌套使用:

try:fh = open("testfile", "w")try:fh.write("这是一个测试文件")finally:print("准备关闭文件")fh.close()
except IOError:print("错误:无法打开或写入文件")

解释:

  1. 内层 try-finally:保证文件关闭。
  2. 外层 try-except:捕获文件操作可能出现的 IOError

执行流程

  • 如果 fh.write() 成功 → finally 执行 → 文件关闭 → 程序继续。
  • 如果 fh.write() 抛异常 → finally 执行 → 文件关闭 → 异常继续传递 → 被外层 except 捕获。

抛出异常

什么是抛出异常(Raising Exceptions)?

在 Python 中,抛出异常指 主动触发一个错误,告诉程序或调用者“这里发生了错误,需要处理”。

  • 作用:
    1. 当程序遇到无法继续执行的情况时,主动报告错误。
    2. 控制程序流程,通过异常处理机制让程序优雅地响应错误。
  • 异常可以是 Python 内置异常,也可以是自定义异常。

抛出内置异常(Built-in Exceptions)

Python 提供了很多内置异常,如 ValueErrorTypeErrorZeroDivisionError 等。
可以使用 raise 语句主动抛出:

raise Exception("这是一个通用异常")

示例:抛出 ValueError

def divide(a, b):if b == 0:raise ValueError("不能除以零")return a / btry:result = divide(10, 0)
except ValueError as e:print(e)

输出:

不能除以零

解释:

  • b 为 0 时,主动抛出 ValueError
  • try-except 捕获异常并处理,程序不会崩溃。

抛出自定义异常(Custom Exceptions)

有些情况,内置异常不能准确描述错误,这时可以定义自己的异常类:

class MyCustomError(Exception):passdef risky_function():raise MyCustomError("risky_function 出错了")try:risky_function()
except MyCustomError as e:print(e)

输出:

risky_function 出错了

解释:

  • 自定义异常类继承自 Exception
  • 可以在函数中主动抛出自定义异常,并在调用处捕获。

带参数的自定义异常

自定义异常可以携带更多信息,让异常更有意义。

class InvalidAgeError(Exception):def __init__(self, age, message="年龄必须在18到100之间"):self.age = ageself.message = messagesuper().__init__(self.message)def set_age(age):if age < 18 or age > 100:raise InvalidAgeError(age)print(f"年龄设置为 {age}")try:set_age(150)
except InvalidAgeError as e:print(f"无效的年龄: {e.age}. {e.message}")

输出:

无效的年龄: 150. 年龄必须在18到100之间

解释:

  • InvalidAgeError 带有 agemessage 属性。
  • 异常被捕获后,可以访问异常对象的属性获取详细信息。

重新抛出异常(Re-Raising Exceptions)

有时我们希望捕获异常做一些处理(例如日志、清理),但仍让异常继续向上抛给更高层处理。

def process_file(filename):try:with open(filename, "r") as file:data = file.read()except FileNotFoundError as e:print(f"文件未找到: {filename}")# 重新抛出异常raise  try:process_file("nonexistentfile.txt")
except FileNotFoundError as e:print("在更高层处理异常")

输出:

文件未找到: nonexistentfile.txt
在更高层处理异常

解释:

  • raise 不带参数时,会重新抛出当前捕获的异常。
  • 常用于局部处理 + 全局处理模式。

异常链

什么是异常链(Exception Chaining)

异常链指的是:在处理一个异常时,如果另一个异常发生,可以将原来的异常作为新异常的一部分保留,从而形成“链式异常”。

作用:

  1. 在处理异常的过程中,保留原始异常信息。
  2. 方便调试,能够看到“原始异常”和“新异常”的完整信息。
  3. 可用于将底层异常转换成更高级别或更语义化的异常。

假设我们尝试打开一个不存在的文件,处理异常时又发生了新的异常:

try:open("nofile.txt")
except OSError:raise RuntimeError("无法处理错误")

输出:

Traceback (most recent call last):File "example.py", line 2, in <module>open("nofile.txt")
FileNotFoundError: [Errno 2] No such file or directory: 'nofile.txt'During handling of the above exception, another exception occurred:Traceback (most recent call last):File "example.py", line 4, in <module>raise RuntimeError("无法处理错误")
RuntimeError: 无法处理错误

说明:

  • Python 自动把原始异常 FileNotFoundError 保存起来,显示在“During handling of the above exception”中。
  • 这种机制就是 异常链 的基础。

使用 raise ... from 显式异常链

Python 3.x 提供了 raise ... from ... 语法,可以明确指定新异常是由哪个原始异常引起的。

try:open("nofile.txt")
except OSError as exc:raise RuntimeError("处理文件时发生错误") from exc

输出:

Traceback (most recent call last):File "example.py", line 2, in <module>open("nofile.txt")
FileNotFoundError: [Errno 2] No such file or directory: 'nofile.txt'The above exception was the direct cause of the following exception:Traceback (most recent call last):File "example.py", line 4, in <module>raise RuntimeError("处理文件时发生错误") from exc
RuntimeError: 处理文件时发生错误

说明:

  • from exc 明确表示新异常 直接由 exc 引起
  • 异常信息中会显示“direct cause”。

使用 raise ... from None 取消异常链

如果你不希望保留原始异常的信息,可以用 from None

try:open("nofile.txt")
except OSError:raise RuntimeError("新异常") from None

输出:

Traceback (most recent call last):File "example.py", line 4, in <module>
RuntimeError: 新异常

说明:

  • 原始异常 OSError 不再显示。
  • 用于屏蔽底层异常,只保留新异常信息。

__context____cause__ 属性

Python 为每个异常对象维护了两个属性,用于表示异常链:

属性说明
__context__自动保存上一个异常,即在 except 块中再次抛出异常时,Python 会把原异常放在这里
__cause__使用 raise ... from ... 显式指定的异常因果关系

示例:

try:try:raise ValueError("ValueError")except ValueError as e1:raise TypeError("TypeError") from e1
except TypeError as e2:print("异常对象:", repr(e2))print("__context__:", repr(e2.__context__))print("__cause__:", repr(e2.__cause__))

输出:

异常对象: TypeError('TypeError')
__context__: ValueError('ValueError')
__cause__: ValueError('ValueError')

说明:

  • __context__:自动保存前一个异常。
  • __cause__:显式指定前一个异常。
  • 异常链有助于 同时保留原始异常和新异常信息

嵌套 try 块

什么是嵌套 try 块(Nested try Block)

嵌套 try 块指的是:在一个 try 块或 except 块内部,再写一个 try-except 或 try-except-finally 结构。

用途:

  1. 不同层级的代码可能引发不同类型的异常。
  2. 可以在内部 try 块处理局部异常,而外部 try 块处理更高层次的异常。
  3. 保证每层的 finally 块都能正常执行,进行必要的资源清理。

单层 try-except-finally 示例

先复习一个简单的 try-except-finally:

a = 10
b = 0
try:print(a / b)
except Exception:print("General Exception")
finally:print("inside outer finally block")

输出:

General Exception
inside outer finally block

说明:

  • a/b 抛出 ZeroDivisionError
  • except 捕获了异常并打印信息。
  • finally 块无论是否异常都会执行。

嵌套 try 示例

示例 1:内层 try 没异常,外层处理异常

a = 10
b = 0
try:print(a / b)try:print("This is inner try block")except Exception:print("General exception")finally:print("inside inner finally block")
except ZeroDivisionError:print("Division by 0")
finally:print("inside outer finally block")

输出:

Division by 0
inside outer finally block

说明:

  • 外层 try 的 a/b 抛出 ZeroDivisionError
  • 内层 try 未被执行,因为异常发生在外层 try 之前。
  • finally 块按照层级顺序执行。

示例 2:异常在内层 try,被内层 except 捕获

a = 10
b = 0
try:print("This is outer try block")try:print(a / b)except ZeroDivisionError:print("Division by 0")finally:print("inside inner finally block")
except Exception:print("General Exception")
finally:print("inside outer finally block")

输出:

This is outer try block
Division by 0
inside inner finally block
inside outer finally block

说明:

  • 外层 try 没有异常。
  • 内层 try 的 a/b 抛出 ZeroDivisionError,被内层 except 捕获。
  • 外层 except 不被调用。
  • finally 块从内到外依次执行。

示例 3:内层 except 不匹配,外层处理异常

a = 10
b = 0
try:print("This is outer try block")try:print(a / b)except KeyError:print("Key Error")finally:print("inside inner finally block")
except ZeroDivisionError:print("Division by 0")
finally:print("inside outer finally block")

输出:

This is outer try block
inside inner finally block
Division by 0
inside outer finally block

说明:

  • 内层 try 的异常是 ZeroDivisionError
  • 内层 except 捕获 KeyError,不匹配 → 异常向外传播。
  • 外层 except 捕获 ZeroDivisionError → 处理异常。
  • finally 块依次执行:内层 → 外层。

嵌套 try 的执行顺序总结

  1. 异常发生顺序
    • 异常在某个 try 块发生时,Python 先找当前 try 的 except 是否匹配。
    • 如果不匹配,异常向外层传播。
  2. finally 块执行顺序
    • 无论异常是否发生,finally 块都会执行。
    • 内层 finally 先执行,再执行外层 finally。
  3. 异常传递
    • 内层 try 的异常如果被捕获 → 不会传递到外层。
    • 内层 try 的异常如果没被捕获 → 传递到外层 try。

自定义异常

什么是用户自定义异常?

在 Python 中,异常(Exception) 是程序运行过程中出现错误的统一机制。
除了内置的异常(如 ValueErrorTypeErrorZeroDivisionError 等),我们也可以 自定义异常类,用来表达更具体、更符合业务逻辑的错误。

好处:

  1. 清晰度(Clarity) → 错误信息更明确,能快速定位问题。
  2. 精细化(Granularity) → 可以区分不同的错误类型,按需处理。
  3. 可维护性(Maintainability) → 将错误逻辑集中管理,代码更易读、易扩展。

定义用户自定义异常的步骤

  1. 定义异常类(继承自 Exception)

    class MyCustomError(Exception):pass
    

    说明:

    • 自定义异常类必须继承自 Exception 或其子类。
    • 如果只是标记错误,可以直接用 pass
  2. 添加初始化方法(携带错误信息)

    class InvalidAgeError(Exception):def __init__(self, age, message="Age must be between 18 and 100"):self.age = ageself.message = messagesuper().__init__(self.message)
    

    说明:

    • age 保存具体的错误值。
    • message 保存错误提示。
    • super().__init__(self.message) 调用父类初始化,让异常能正常打印。
  3. 可选:重写 __str__(自定义打印格式)

    class InvalidAgeError(Exception):def __init__(self, age, message="Age must be between 18 and 100"):self.age = ageself.message = messagesuper().__init__(self.message)def __str__(self):return f"{self.message}. Provided age: {self.age}"
    

    说明:

    • __str__ 方法决定了 print(e)str(e) 的显示效果。
    • 可以把更多上下文信息拼接到错误提示里。

抛出用户自定义异常

语法:

raise ExceptionType(args)

例子:

def set_age(age):if age < 18 or age > 100:raise InvalidAgeError(age)  # 抛出自定义异常print(f"Age is set to {age}")

如果传入 set_age(150),程序会抛出 InvalidAgeError

捕获用户自定义异常

和捕获内置异常一样,用 try-except

try:set_age(150)
except InvalidAgeError as e:print(f"Invalid age: {e.age}. {e.message}")

输出:

Invalid age: 150. Age must be between 18 and 100

完整示例

class InvalidAgeError(Exception):def __init__(self, age, message="Age must be between 18 and 100"):self.age = ageself.message = messagesuper().__init__(self.message)def __str__(self):return f"{self.message}. Provided age: {self.age}"def set_age(age):if age < 18 or age > 100:raise InvalidAgeError(age)   # 抛出异常print(f"Age is set to {age}")try:set_age(150)
except InvalidAgeError as e:print(f"捕获到自定义异常 → {e}")

运行结果:

捕获到自定义异常 → Age must be between 18 and 100. Provided age: 150

高级用法

  1. 层级化异常
    你可以设计一组异常,形成继承关系:

    class AppError(Exception): pass
    class DatabaseError(AppError): pass
    class ValidationError(AppError): pass
    

    ✅ 这样可以:

    • 精确捕获:except ValidationError
    • 统一捕获:except AppError
  2. 结合日志系统
    在异常类中直接内置日志记录,方便调试:

    import loggingclass LoggedError(Exception):def __init__(self, message):super().__init__(message)logging.error(f"Error occurred: {message}")
    
  3. 异常携带更多上下文信息
    除了 message,还可以保存函数名、时间戳、输入参数等,便于追踪。

    class DetailedError(Exception):def __init__(self, func, param, message="Error in function"):self.func = funcself.param = paramself.message = messagesuper().__init__(f"{message} {func} with param {param}")
    

日志

什么是 Logging?

在程序运行过程中,我们常常需要记录一些信息,比如:

  • 出现了什么错误?
  • 某段代码是否执行了?
  • 用户输入了什么?
  • 系统运行情况如何?

Logging(日志记录) 就是解决这些问题的。它是 比 print() 更专业、更灵活 的工具。
Python 内置了 logging 模块,可以把日志信息输出到:

  • 控制台(屏幕)
  • 文件
  • 网络
  • 邮件
  • 系统日志服务

为什么要用 Logging 而不是 print()?

  1. 灵活性

    • print() 只能打印到控制台

    • logging 可以选择输出到文件、邮件、服务器等

  2. 等级控制

    • print() 只能输出一类信息

    • loggingDEBUG, INFO, WARNING, ERROR, CRITICAL 五个等级,方便区分严重程度

  3. 规范化

    • print() 只能输出一串文字

    • logging 可以格式化输出:时间戳、模块名、函数名、行号、日志等级等

总结:在 调试阶段 你可能用 print(),但在 实际项目 一般都用 logging

Logging 的核心组件

  1. Logger:日志入口,负责创建日志对象。
  2. Handler:决定日志发送到哪里(控制台、文件、网络等)。
  3. Formatter:决定日志的显示格式。
  4. Level(日志级别):决定日志的严重性,低于设置级别的日志不会被记录。
  5. Filter:用于过滤日志(进阶功能)。

日志等级(从低到高)

级别用途
DEBUG10调试信息,记录最详细的执行情况
INFO20普通运行信息,确认程序按预期工作
WARNING30警告,提示潜在问题
ERROR40错误,某个功能没执行成功
CRITICAL50严重错误,可能导致程序终止

日志等级是 层级继承 的,比如设置成 INFO,就不会显示 DEBUG

入门示例

import logging# 配置日志:级别 + 格式
logging.basicConfig(level=logging.DEBUG,  format="%(asctime)s - %(levelname)s - %(message)s"
)def calculate_sum(a, b):logging.debug(f"正在计算 {a} + {b}")result = a + blogging.info(f"结果是 {result}")return resultif __name__ == "__main__":logging.info("程序启动")calculate_sum(10, 20)logging.warning("这个是警告示例")logging.error("这个是错误示例")logging.critical("这个是严重错误示例")

输出结果:

2025-09-20 14:10:45,123 - INFO - 程序启动
2025-09-20 14:10:45,124 - DEBUG - 正在计算 10 + 20
2025-09-20 14:10:45,124 - INFO - 结果是 30
2025-09-20 14:10:45,124 - WARNING - 这个是警告示例
2025-09-20 14:10:45,124 - ERROR - 这个是错误示例
2025-09-20 14:10:45,124 - CRITICAL - 这个是严重错误示例

自定义 Logger、Handler、Formatter

import logging# 创建 logger
logger = logging.getLogger("my_app")
logger.setLevel(logging.DEBUG)# 创建控制台 handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)# 创建文件 handler
file_handler = logging.FileHandler("app.log", encoding="utf-8")
file_handler.setLevel(logging.INFO)# 创建 formatter
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)# 添加 handler
logger.addHandler(console_handler)
logger.addHandler(file_handler)# 使用
logger.debug("调试信息")
logger.info("一般信息")
logger.warning("警告信息")
logger.error("错误信息")
logger.critical("严重错误信息")

结果:

  • 控制台会输出所有等级日志
  • 文件 app.log 只会记录 INFO 及以上等级

常用的 Handler

Handler作用
StreamHandler输出到控制台
FileHandler输出到文件
RotatingFileHandler文件大小超限时自动切割
TimedRotatingFileHandler按时间切割日志(每天一个日志文件)
SMTPHandler发送日志邮件
HTTPHandler发送日志到远程服务器
SysLogHandler发送到系统日志

示例:按大小切割日志文件

from logging.handlers import RotatingFileHandlerhandler = RotatingFileHandler("app.log", maxBytes=2000, backupCount=3, encoding="utf-8"
)
logger.addHandler(handler)

断言

什么是断言(Assertion)?

在编程中,断言就是一种“自我检查”:你告诉程序,“这里的条件必须为真,如果不为真,就说明程序逻辑出错了!”

在 Python 中,断言用 assert 关键字实现。

核心点:

  • 如果条件为 True:什么都不会发生,程序继续执行。
  • 如果条件为 False:会抛出 AssertionError,并且程序停止运行(除非捕获)。

为什么要用断言?

  1. 调试:快速发现程序逻辑错误。
  2. 保证前置条件/后置条件:函数输入/输出符合要求。
  3. 文档化:让代码“自我说明”,别人一看就知道约束条件。

注意:断言 不是用来替代异常处理的

  • 断言:用来捕捉 开发时的逻辑错误
  • 异常:用来处理 运行时的不确定情况(比如文件不存在、网络超时)。

语法

assert condition, message
  • condition:布尔表达式,必须为 True。
  • message:可选,断言失败时的错误提示。

简单示例

print("请输入分数(0~100):")
num = 75
assert num >= 0 and num <= 100
print("分数:", num)num = 125
assert num >= 0 and num <= 100
print("分数:", num)  # 不会执行

输出:

Traceback (most recent call last):File "/Users/huangruibang/PycharmProjects/PythonLearn/Pythondemo/main.py", line 7, in <module>assert 0 <= num <= 100^^^^^^^^^^^^^^^
AssertionError
请输入分数(0~100):
分数: 75

第二次断言失败,程序终止。

自定义错误消息

num = 125
assert num >= 0 and num <= 100, "分数必须在 0~100 之间"

输出:

Traceback (most recent call last):File "/Users/huangruibang/PycharmProjects/PythonLearn/Pythondemo/main.py", line 2, in <module>assert num >= 0 and num <= 100, "分数必须在 0~100 之间"^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 分数必须在 0~100 之间

捕获 AssertionError

断言本质就是抛异常,可以像普通异常一样捕获:

try:num = -5assert num >= 0, "只能输入非负数"print(num)
except AssertionError as e:print("断言失败:", e)

输出:

断言失败: 只能输入非负数

断言 vs 异常

特点断言(assert)异常(try-except)
用途调试 & 开发期自检运行时错误处理
目标捕捉永远不该发生的逻辑错误捕捉可能发生的异常情况
运行可在 -O 模式下关闭永远启用
场景参数校验、函数契约文件I/O错误、网络错误

例子对比:

断言

def square_root(x):assert x >= 0, "x 必须为非负数"return x ** 0.5

异常

def safe_divide(a, b):try:return a / bexcept ZeroDivisionError:return "除数不能为0"

断言的禁用

Python 提供了 -O(optimize)模式运行脚本:

python -O script.py

此时,所有 assert 语句会被忽略(相当于从代码中移除了)。
因此,断言 不能用来保证生产环境的关键逻辑,只能用作调试。

断言的应用场景

  1. 函数参数检查

    def withdraw(balance, amount):assert amount > 0, "取款金额必须大于0"assert amount <= balance, "余额不足"return balance - amount
    
  2. 循环不变量(invariants)

    for i in range(10):assert 0 <= i < 10
    
  3. 函数结果检查

    def factorial(n):result = 1for i in range(1, n + 1):result *= iassert result > 0, "阶乘结果必须大于0"return result
    

警告

什么是 Python 的警告(Warnings)

在 Python 中,警告(Warning) 是一种运行时提示,表示代码中出现了潜在问题未来可能出错的情况

和异常(Error)不同:

  • Error 会中断程序执行。
  • Warning 只是打印提示信息,程序仍然继续执行。

常见用途:

  • 使用了 弃用(deprecated) 的功能(提示开发者未来要替换)。
  • 出现 不规范写法(比如错误的语法用法,但还能运行)。
  • 运行时疑似 bug(但不严重到报错)。

发出警告

使用 warnings.warn() 发出警告。

import warningsprint("程序运行正常")
warnings.warn("你触发了一个警告!")

输出:

程序运行正常
...: UserWarning: 你触发了一个警告!warnings.warn("你触发了一个警告!")

解释:

  • 默认情况下,警告类别是 UserWarning
  • 警告会显示 文件名 + 行号 + 警告类别 + 信息

常见警告类别(Warning Classes)

Python 中的警告基于 异常类体系,不同类型代表不同问题。

警告类别说明示例
UserWarning默认警告类别warnings.warn("普通警告")
DeprecationWarning功能弃用(未来版本将移除)使用老旧函数
SyntaxWarning语法问题,但不报错x is 10(错误用法)
RuntimeWarning运行时问题(潜在 bug)数学溢出
ImportWarning模块导入问题循环导入风险
FutureWarning某些功能未来行为将变化版本升级提示
PendingDeprecationWarning功能即将被弃用(比 DeprecationWarning 更提前的提醒)开发预警

示例:

import warningswarnings.warn("普通用户警告", UserWarning)
warnings.warn("这个功能将被弃用", DeprecationWarning)
warnings.warn("潜在语法问题", SyntaxWarning)
warnings.warn("运行时有风险", RuntimeWarning)
warnings.warn("导入模块有风险", ImportWarning)

警告过滤器(Warning Filters)

有时候我们不希望看到一堆警告,可以通过 过滤器 来控制显示方式。

方法:warnings.filterwarnings(action, ...)

常见 action 值:

  • "default":默认行为,只显示一次。
  • "always":每次都显示。
  • "ignore":忽略该警告。
  • "error":将警告当作异常抛出。
  • "module":每个模块第一次触发时显示一次。
  • "once":只显示一次,无论在哪。

示例:

import warnings# 1. 忽略所有警告
warnings.filterwarnings("ignore")
warnings.warn("这条不会显示")# 2. 将警告变成错误
warnings.filterwarnings("error", category=UserWarning)
try:warnings.warn("现在是错误!", UserWarning)
except UserWarning as e:print("捕获到异常:", e)

警告模块的常用函数

函数功能
warnings.warn()发出警告
warnings.showwarning()自定义显示方式(比如写入文件)
warnings.warn_explicit()更精确地控制(指定文件、行号、模块名)
warnings.filterwarnings()过滤器(最常用)
warnings.simplefilter()简化版过滤器
warnings.resetwarnings()重置为默认状态

示例:写入文件

import warningswith open("warn.log", "w") as f:warnings.showwarning("写入日志的警告", UserWarning, "example.py", 10, file=f)

示例:更精确控制

import warningswarnings.warn_explicit("自定义警告", UserWarning,filename="my_module.py", lineno=42,module="my_module"
)

捕获和处理警告

警告本身不会中断程序,但有时我们想 捕获它们,比如在测试时。

使用 warnings.catch_warnings()

import warningswith warnings.catch_warnings(record=True) as w:warnings.simplefilter("always")  # 保证捕获warnings.warn("这是捕获的警告")print("捕获到的警告:", w[0].message)

输出:

捕获到的警告: 这是捕获的警告

实际应用场景

  1. 弃用旧功能

    def old_func():import warningswarnings.warn("old_func 已弃用,请使用 new_func", DeprecationWarning)
    
  2. 运行时检查

    import warningsdef divide(a, b):if b == 0:warnings.warn("除数为 0,结果可能无效", RuntimeWarning)return float("inf")return a / b
    
  3. 调试时辅助

    • 在单元测试时,可以捕获警告,确保新旧版本兼容。
    • 在库开发中,提前告知用户某些 API 将被移除。

Warnings vs Exceptions 的区别

特点警告(Warning)异常(Exception)
是否中断程序❌ 不会✅ 会
触发方式warnings.warn()raise Exception
使用场景提示潜在问题、弃用提醒严重错误、不可恢复的情况
是否可捕获✅(catch_warnings)✅(try-except)

内置异常

在 Python 中,异常(Exception) 表示程序运行过程中遇到的错误情况。

  • 错误(Error) 是严重的问题(比如语法写错,无法继续执行)。
  • 异常(Exception) 是可以捕获、处理的运行时问题(比如除以零、访问不存在的字典键等)。

Python 提供了大量内置异常类,统一继承自 BaseException,这使得异常可以分层管理。

异常类之间有继承关系,常见层级如下:

BaseException├── SystemExit├── KeyboardInterrupt└── Exception├── ArithmeticError│    ├── FloatingPointError│    ├── OverflowError│    └── ZeroDivisionError├── AssertionError├── AttributeError├── EOFError├── ImportError│    └── ModuleNotFoundError├── LookupError│    ├── IndexError│    └── KeyError├── NameError│    └── UnboundLocalError├── OSError│    ├── FileNotFoundError│    └── PermissionError├── RuntimeError│    └── NotImplementedError├── SyntaxError│    └── IndentationError├── TypeError├── ValueError│    └── UnicodeError└── ...

常见内置异常及示例

异常类触发场景示例
ZeroDivisionError除数为 01/0
IndexError列表越界[1,2][3]
KeyError字典中找不到键{'a':1}['b']
NameError变量未定义print(x)
UnboundLocalError函数中引用未赋值局部变量python def f(): print(x); x=5; f()
TypeError类型不匹配'1'+2
ValueError值不合法int('abc')
AttributeError调用不存在的方法"abc".append(1)
ImportError导入失败from math import cube
ModuleNotFoundError模块不存在import notamodule
EOFErrorinput() 遇到文件结尾模拟输入结束
FileNotFoundError文件不存在open("nofile.txt")
PermissionError文件权限不足打开只读文件写入
AssertionErrorassert 失败assert 1==2
RuntimeError运行时通用错误raise RuntimeError(“something wrong”)
NotImplementedError抽象方法未实现在基类方法里写 raise NotImplementedError
KeyboardInterrupt用户按下 Ctrl+C在运行时手动中断
SystemExitsys.exit() 被调用退出解释器

使用内置异常

  1. 基本捕获

    try:x = 1 / 0
    except ZeroDivisionError as e:print("捕获异常:", e)
    

    输出:

    捕获异常: division by zero
    
  2. 捕获多个异常

    try:y = int("abc")
    except (ValueError, TypeError) as e:print("捕获 ValueError 或 TypeError:", e)
    
  3. 使用 elsefinally

    try:num = int(input("请输入一个数字: "))
    except ValueError:print("输入无效!")
    else:print("你输入的是:", num)
    finally:print("无论如何都会执行,比如关闭文件")
    
  4. 主动抛出异常(raise)

    def divide(a, b):if b == 0:raise ZeroDivisionError("不能除以零")return a / btry:print(divide(5, 0))
    except ZeroDivisionError as e:print("自定义异常信息:", e)
    
http://www.dtcms.com/a/393372.html

相关文章:

  • 【状态机实现】初识——基于状态机实现的流程编排和Activiti、Camunda、Flowable等工作流的区别
  • SpringBoot自动配置核心原理
  • Python 中的 Builder 模式实践 —— 以 UserProfileBuilder 为例
  • 探秘陌讯AIGC检测算法优化:详解MPS加速与模型热重载的实现原理
  • 1.3 管道(Pipe)核心知识点总结
  • GLUE:自然语言理解评估的黄金基准
  • 第13章 智能监测-设备数据处理
  • GEO技术科普
  • B004基于三菱FX2NPLC智能自提柜控制系统仿真
  • MTK CPU温度调节一知半解
  • V90伺服驱动器“速度模式“双极性模拟量速度控制
  • 课前练习题-20250919
  • C++类与对象
  • 企业级Docker镜像仓库Harbor
  • ESD防护设计宝典(七):生命线的秩序——关键信号线布线规则
  • 【ROS2】Beginner : CLI tools - 理解 ROS 2 话题
  • RL知识回顾
  • Java多线程编程指南
  • 【论文速读】基于地面激光扫描(TLS)和迭 代最近点(ICP)算法的土坝监测变形分析
  • GAMES101:现代计算机图形学入门(Chapter2 向量与线性代数)迅猛式学线性代数学习笔记
  • 汉语构词智慧:从历史优势到现实考量——兼论“汉语全面改造英语”的可能性
  • 仿tcmalloc高并发内存池
  • 墨者学院-通关攻略(持续更新持续改进)
  • 10厘米钢板矫平机:把“波浪”压成“镜面”的科学
  • ESP32- 项目应用1 智能手表之网络配置 #6
  • TCP/IP 互联网的真相:空间域和时间域的统计学
  • 同步与异步
  • C++中char与string的终极对比指南
  • Java基础 9.20
  • U228721 反转单链表