【Python】错误和异常
错误和异常
语法错误
什么是语法错误?
在 Python 中,语法错误(Syntax Error) 是最常见的一类错误。
它发生在你编写的代码 不符合 Python 语法规则 时。
- 检测阶段:语法错误会在 代码解析阶段(parsing) 被解释器发现,程序根本不会开始执行。
- 本质原因:你的代码不符合 Python 的“语法文法”,导致解释器无法理解你的指令。
举例:
if True
print("Hello")
运行会报错:
File "test.py", line 1
if True
^
SyntaxError: expected ':'
解释器告诉我们:if 后面缺少冒号。
常见的语法错误原因
-
缺少冒号 (😃
在
if、for、while、def、class等语句后必须加冒号,否则报错。# ❌ 错误 if True print("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 1 print("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-excepttry: # 可能出错的代码 result = 10 / 0 except ZeroDivisionError: # 出错后执行的代码 print("不能除以零")输出:
不能除以零解释:
try:放置可能出错的代码。except:指定要捕获的异常类型,并写上出错后的处理方式。
-
多个
excepttry: 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 / b
try:
print(divide(10, 0))
except ZeroDivisionError as e:
print("错误:", e)
输出:
错误: 分母不能为零
自定义异常
Python 允许我们创建自己的异常类(必须继承 Exception)。
class MyError(Exception):
def __init__(self, message):
self.message = message
try:
raise MyError("这是自定义错误")
except MyError as e:
print("捕获自定义异常:", e.message)
输出:
捕获自定义异常: 这是自定义错误
断言(Assertions)
断言是异常处理的一种简化形式,用于 检查条件是否满足。
如果条件不满足,Python 会抛出 AssertionError。
def KelvinToFahrenheit(temp):
assert temp >= 0, "温度不能低于绝对零度"
return (temp - 273) * 1.8 + 32
print(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 / number
print("结果:", result)
except ZeroDivisionError:
print("错误:不能除以 0")
多个异常处理
try:
number = int(input("请输入一个数字:"))
result = 10 / number
print("结果:", 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 / b
try:
result = divide(10, 0)
except ValueError as e:
print(e)
输出:
不能除以零
解释:
- 当
b为 0 时,主动抛出ValueError。 try-except捕获异常并处理,程序不会崩溃。
抛出自定义异常(Custom Exceptions)
有些情况,内置异常不能准确描述错误,这时可以定义自己的异常类:
class MyCustomError(Exception):
pass
def 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 = age
self.message = message
super().__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 = age self.message = message super().__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 = age self.message = message super().__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 = age
self.message = message
super().__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 logging class 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 = func self.param = param self.message = message super().__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 + b
logging.info(f"结果是 {result}")
return result
if __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 RotatingFileHandler
handler = 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 = -5
assert 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 / b
except 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 = 1 for i in range(1, n + 1): result *= i assert result > 0, "阶乘结果必须大于0" return result
警告
什么是 Python 的警告(Warnings)
在 Python 中,警告(Warning) 是一种运行时提示,表示代码中出现了潜在问题或未来可能出错的情况。
和异常(Error)不同:
- Error 会中断程序执行。
- Warning 只是打印提示信息,程序仍然继续执行。
常见用途:
- 使用了 弃用(deprecated) 的功能(提示开发者未来要替换)。
- 出现 不规范写法(比如错误的语法用法,但还能运行)。
- 运行时疑似 bug(但不严重到报错)。
发出警告
使用 warnings.warn() 发出警告。
import warnings
print("程序运行正常")
warnings.warn("你触发了一个警告!")
输出:
程序运行正常
...: UserWarning: 你触发了一个警告!
warnings.warn("你触发了一个警告!")
解释:
- 默认情况下,警告类别是
UserWarning。 - 警告会显示 文件名 + 行号 + 警告类别 + 信息。
常见警告类别(Warning Classes)
Python 中的警告基于 异常类体系,不同类型代表不同问题。
| 警告类别 | 说明 | 示例 |
|---|---|---|
UserWarning | 默认警告类别 | warnings.warn("普通警告") |
DeprecationWarning | 功能弃用(未来版本将移除) | 使用老旧函数 |
SyntaxWarning | 语法问题,但不报错 | x is 10(错误用法) |
RuntimeWarning | 运行时问题(潜在 bug) | 数学溢出 |
ImportWarning | 模块导入问题 | 循环导入风险 |
FutureWarning | 某些功能未来行为将变化 | 版本升级提示 |
PendingDeprecationWarning | 功能即将被弃用(比 DeprecationWarning 更提前的提醒) | 开发预警 |
示例:
import warnings
warnings.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 warnings
with open("warn.log", "w") as f:
warnings.showwarning("写入日志的警告", UserWarning, "example.py", 10, file=f)
示例:更精确控制
import warnings
warnings.warn_explicit(
"自定义警告", UserWarning,
filename="my_module.py", lineno=42,
module="my_module"
)
捕获和处理警告
警告本身不会中断程序,但有时我们想 捕获它们,比如在测试时。
使用 warnings.catch_warnings():
import warnings
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always") # 保证捕获
warnings.warn("这是捕获的警告")
print("捕获到的警告:", w[0].message)
输出:
捕获到的警告: 这是捕获的警告
实际应用场景
-
弃用旧功能
def old_func(): import warnings warnings.warn("old_func 已弃用,请使用 new_func", DeprecationWarning) -
运行时检查
import warnings def 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和finallytry: num = int(input("请输入一个数字: ")) except ValueError: print("输入无效!") else: print("你输入的是:", num) finally: print("无论如何都会执行,比如关闭文件") -
主动抛出异常(raise)
def divide(a, b): if b == 0: raise ZeroDivisionError("不能除以零") return a / b try: print(divide(5, 0)) except ZeroDivisionError as e: print("自定义异常信息:", e)
更多推荐
所有评论(0)