【Python】错误和异常
目录
- 错误和异常
- 语法错误
- 异常处理
- try-except 块
- try-finally 块
- 抛出异常
- 异常链
- 嵌套 try 块
- 自定义异常
- 日志
- 断言
- 警告
- 内置异常
错误和异常
语法错误
什么是语法错误?
在 Python 中,语法错误(Syntax Error) 是最常见的一类错误。
它发生在你编写的代码 不符合 Python 语法规则 时。
- 检测阶段:语法错误会在 代码解析阶段(parsing) 被解释器发现,程序根本不会开始执行。
- 本质原因:你的代码不符合 Python 的“语法文法”,导致解释器无法理解你的指令。
举例:
if Trueprint("Hello")
运行会报错:
File "test.py", line 1if True^
SyntaxError: expected ':'
解释器告诉我们:if
后面缺少冒号。
常见的语法错误原因
-
缺少冒号 (😃
在
if
、for
、while
、def
、class
等语句后必须加冒号,否则报错。# ❌ 错误 if Trueprint("Yes")
报错:
SyntaxError: expected ':'
修复:
# ✅ 正确 if True:print("Yes")
-
缩进错误(Indentation Error)
Python 依靠 缩进 来表示代码块。缩进不正确会报语法错误。
# ❌ 错误 def test(): print("Hello")
报错:
IndentationError: expected an indented block
修复:
# ✅ 正确 def test():print("Hello")
-
拼写错误或关键字使用错误
# ❌ 错误 prnt("Hello")
报错:
NameError: name 'prnt' is not defined
虽然这不是严格的 SyntaxError,但属于常见的新手错误。
正确写法:print("Hello")
-
括号、方括号、大括号不匹配
Python 要求括号必须成对出现。
# ❌ 错误 print("Hello"
报错:
SyntaxError: '(' was never closed
修复:
print("Hello")
如何定位语法错误?
-
阅读错误信息
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 → 字符串没有正确结束(缺少引号或括号)
-
IDE 帮助
使用 IDE(集成开发环境)能在写代码时及时发现语法错误。常见特性:
- 语法高亮:不同语法元素显示不同颜色,异常着色可能提示错误。
- 错误下划线:错误位置通常有红色波浪线。
- Lint 工具(如
pylint
、flake8
):自动检测语法问题。
常用 IDE:PyCharm、VS Code、Jupyter Notebook。
-
分块运行代码
如果脚本很大,可以 逐步运行小片段,快速定位错误位置。
例如一个文件有多个函数,可以单独运行某个函数来排查。 -
使用版本控制:如果你用 Git 管理代码,可以比较版本差异,找到最近引入语法错误的修改。
修复语法错误的方法
修复步骤:
- 仔细阅读错误信息
Python 会告诉你文件名、行号和错误原因。 - 定位代码位置
转到报错行,有时错误在上一行(比如缺冒号导致下一行报错)。 - 理解错误类型
常见的有:expected ':'
→ 缺冒号EOL while scanning string literal
→ 字符串没有闭合unexpected indent
→ 缩进错误
- 修改代码
根据提示修复,再次运行。
异常处理
什么是异常(Exception)?
在 Python 中,异常就是程序运行过程中出现的错误,它会打断程序的正常执行流程。
和 语法错误(Syntax Error) 不同,语法错误在程序运行前就会被检测出来,而 异常是在程序运行时 触发的。
例如:
print(10 / 0) # 除以零
运行结果:
ZeroDivisionError: division by zero
这里 ZeroDivisionError
就是异常。
总结:
- 语法错误(Syntax Error):解释器无法理解代码,程序不能运行。
- 运行时异常(Runtime Exception):程序能运行,但遇到非法操作时会抛出异常。
为什么要处理异常?
如果程序中出现异常但未处理,Python 会直接报错并终止程序。
为了让程序更加健壮,我们需要使用 异常处理机制 来捕获错误,给出合理的应对方式,而不是让程序崩溃。
异常处理的语法
Python 用 try…except…else…finally 语句来进行异常处理。
-
基本用法:
try-except
try:# 可能出错的代码result = 10 / 0 except ZeroDivisionError:# 出错后执行的代码print("不能除以零")
输出:
不能除以零
解释:
try
:放置可能出错的代码。except
:指定要捕获的异常类型,并写上出错后的处理方式。
-
多个
except
try:x = int("abc") # 会触发 ValueError except ZeroDivisionError:print("除以零错误") except ValueError:print("数值转换错误")
输出:
数值转换错误
-
捕获所有异常
try:x = 10 / 0 except Exception as e:print("出错啦:", e)
输出:
出错啦: division by zero
注意:
except Exception
会捕获所有标准异常,但不推荐总是用它,因为这样可能掩盖真正的错误。 -
else
子句else
代码块在 try 中没有异常时执行。try:num = int("123") except ValueError:print("数值转换错误") else:print("转换成功:", num)
输出:
转换成功: 123
-
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 工作流程
- 执行
try
块里的代码。 - 如果没有异常 → 跳过
except
,执行else
(如果有)。 - 如果发生异常 → 进入匹配的
except
,然后跳过else
。 - 最后一定会执行
finally
(如果有)。
- 执行
-
异常的传播
如果try
块中没有捕获异常,Python 会向调用者一层层“抛出”,直到最顶层。如果仍然没捕获,程序就会终止。
注意事项
-
捕获具体异常,而不是用裸
except:
❌ 不推荐:try:x = 10 / 0 except:print("出错了")
这样会把所有异常(甚至
KeyboardInterrupt
)都捕获掉,不利于排查问题。✅ 推荐:
try:x = 10 / 0 except ZeroDivisionError:print("不能除以 0")
-
用
as e
打印异常信息try:int("abc") except ValueError as e:print("错误信息:", e)
输出:
错误信息: invalid literal for int() with base 10: 'abc'
-
合理使用 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()
解释:
- 打开文件并写入内容。
- 无论写入成功还是失败,
finally
都会执行,文件会被关闭。 - 这保证了资源不会泄漏。
嵌套使用 try-finally
和 try-except
为了同时保证资源清理并捕获异常,可以嵌套使用:
try:fh = open("testfile", "w")try:fh.write("这是一个测试文件")finally:print("准备关闭文件")fh.close()
except IOError:print("错误:无法打开或写入文件")
解释:
- 内层
try-finally
:保证文件关闭。 - 外层
try-except
:捕获文件操作可能出现的IOError
。
执行流程:
- 如果
fh.write()
成功 → finally 执行 → 文件关闭 → 程序继续。 - 如果
fh.write()
抛异常 → finally 执行 → 文件关闭 → 异常继续传递 → 被外层 except 捕获。
抛出异常
什么是抛出异常(Raising Exceptions)?
在 Python 中,抛出异常指 主动触发一个错误,告诉程序或调用者“这里发生了错误,需要处理”。
- 作用:
- 当程序遇到无法继续执行的情况时,主动报告错误。
- 控制程序流程,通过异常处理机制让程序优雅地响应错误。
- 异常可以是 Python 内置异常,也可以是自定义异常。
抛出内置异常(Built-in Exceptions)
Python 提供了很多内置异常,如 ValueError
、TypeError
、ZeroDivisionError
等。
可以使用 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
带有age
和message
属性。- 异常被捕获后,可以访问异常对象的属性获取详细信息。
重新抛出异常(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)
异常链指的是:在处理一个异常时,如果另一个异常发生,可以将原来的异常作为新异常的一部分保留,从而形成“链式异常”。
作用:
- 在处理异常的过程中,保留原始异常信息。
- 方便调试,能够看到“原始异常”和“新异常”的完整信息。
- 可用于将底层异常转换成更高级别或更语义化的异常。
假设我们尝试打开一个不存在的文件,处理异常时又发生了新的异常:
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 结构。
用途:
- 不同层级的代码可能引发不同类型的异常。
- 可以在内部 try 块处理局部异常,而外部 try 块处理更高层次的异常。
- 保证每层的 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 的执行顺序总结
- 异常发生顺序:
- 异常在某个 try 块发生时,Python 先找当前 try 的 except 是否匹配。
- 如果不匹配,异常向外层传播。
- finally 块执行顺序:
- 无论异常是否发生,finally 块都会执行。
- 内层 finally 先执行,再执行外层 finally。
- 异常传递:
- 内层 try 的异常如果被捕获 → 不会传递到外层。
- 内层 try 的异常如果没被捕获 → 传递到外层 try。
自定义异常
什么是用户自定义异常?
在 Python 中,异常(Exception) 是程序运行过程中出现错误的统一机制。
除了内置的异常(如 ValueError
、TypeError
、ZeroDivisionError
等),我们也可以 自定义异常类,用来表达更具体、更符合业务逻辑的错误。
好处:
- 清晰度(Clarity) → 错误信息更明确,能快速定位问题。
- 精细化(Granularity) → 可以区分不同的错误类型,按需处理。
- 可维护性(Maintainability) → 将错误逻辑集中管理,代码更易读、易扩展。
定义用户自定义异常的步骤
-
定义异常类(继承自 Exception)
class MyCustomError(Exception):pass
说明:
- 自定义异常类必须继承自
Exception
或其子类。 - 如果只是标记错误,可以直接用
pass
。
- 自定义异常类必须继承自
-
添加初始化方法(携带错误信息)
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)
调用父类初始化,让异常能正常打印。
-
可选:重写
__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
高级用法
-
层级化异常
你可以设计一组异常,形成继承关系:class AppError(Exception): pass class DatabaseError(AppError): pass class ValidationError(AppError): pass
✅ 这样可以:
- 精确捕获:
except ValidationError
- 统一捕获:
except AppError
- 精确捕获:
-
结合日志系统
在异常类中直接内置日志记录,方便调试:import loggingclass LoggedError(Exception):def __init__(self, message):super().__init__(message)logging.error(f"Error occurred: {message}")
-
异常携带更多上下文信息
除了 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()?
-
灵活性
-
print()
只能打印到控制台 -
logging
可以选择输出到文件、邮件、服务器等
-
-
等级控制
-
print()
只能输出一类信息 -
logging
有 DEBUG, INFO, WARNING, ERROR, CRITICAL 五个等级,方便区分严重程度
-
-
规范化
-
print()
只能输出一串文字 -
logging
可以格式化输出:时间戳、模块名、函数名、行号、日志等级等
-
总结:在 调试阶段 你可能用 print()
,但在 实际项目 一般都用 logging
。
Logging 的核心组件
- Logger:日志入口,负责创建日志对象。
- Handler:决定日志发送到哪里(控制台、文件、网络等)。
- Formatter:决定日志的显示格式。
- Level(日志级别):决定日志的严重性,低于设置级别的日志不会被记录。
- Filter:用于过滤日志(进阶功能)。
日志等级(从低到高)
级别 | 值 | 用途 |
---|---|---|
DEBUG | 10 | 调试信息,记录最详细的执行情况 |
INFO | 20 | 普通运行信息,确认程序按预期工作 |
WARNING | 30 | 警告,提示潜在问题 |
ERROR | 40 | 错误,某个功能没执行成功 |
CRITICAL | 50 | 严重错误,可能导致程序终止 |
日志等级是 层级继承 的,比如设置成 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
,并且程序停止运行(除非捕获)。
为什么要用断言?
- 调试:快速发现程序逻辑错误。
- 保证前置条件/后置条件:函数输入/输出符合要求。
- 文档化:让代码“自我说明”,别人一看就知道约束条件。
注意:断言 不是用来替代异常处理的。
- 断言:用来捕捉 开发时的逻辑错误。
- 异常:用来处理 运行时的不确定情况(比如文件不存在、网络超时)。
语法
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 语句会被忽略(相当于从代码中移除了)。
因此,断言 不能用来保证生产环境的关键逻辑,只能用作调试。
断言的应用场景
-
函数参数检查
def withdraw(balance, amount):assert amount > 0, "取款金额必须大于0"assert amount <= balance, "余额不足"return balance - amount
-
循环不变量(invariants)
for i in range(10):assert 0 <= i < 10
-
函数结果检查
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)
输出:
捕获到的警告: 这是捕获的警告
实际应用场景
-
弃用旧功能
def old_func():import warningswarnings.warn("old_func 已弃用,请使用 new_func", DeprecationWarning)
-
运行时检查
import warningsdef divide(a, b):if b == 0:warnings.warn("除数为 0,结果可能无效", RuntimeWarning)return float("inf")return a / b
-
调试时辅助
- 在单元测试时,可以捕获警告,确保新旧版本兼容。
- 在库开发中,提前告知用户某些 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 | 除数为 0 | 1/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 |
EOFError | input() 遇到文件结尾 | 模拟输入结束 |
FileNotFoundError | 文件不存在 | open("nofile.txt") |
PermissionError | 文件权限不足 | 打开只读文件写入 |
AssertionError | assert 失败 | assert 1==2 |
RuntimeError | 运行时通用错误 | raise RuntimeError(“something wrong”) |
NotImplementedError | 抽象方法未实现 | 在基类方法里写 raise NotImplementedError |
KeyboardInterrupt | 用户按下 Ctrl+C | 在运行时手动中断 |
SystemExit | sys.exit() 被调用 | 退出解释器 |
使用内置异常
-
基本捕获
try:x = 1 / 0 except ZeroDivisionError as e:print("捕获异常:", e)
输出:
捕获异常: division by zero
-
捕获多个异常
try:y = int("abc") except (ValueError, TypeError) as e:print("捕获 ValueError 或 TypeError:", e)
-
使用
else
和finally
try:num = int(input("请输入一个数字: ")) except ValueError:print("输入无效!") else:print("你输入的是:", num) finally:print("无论如何都会执行,比如关闭文件")
-
主动抛出异常(raise)
def divide(a, b):if b == 0:raise ZeroDivisionError("不能除以零")return a / btry:print(divide(5, 0)) except ZeroDivisionError as e:print("自定义异常信息:", e)