Python 10 大常见错误解析与修复实战!
写 Python 代码时,谁没遇到过红色报错?尤其是刚入门的时候,看到满屏错误提示能慌半天 ——“这啥意思?我代码看着没问题啊!” 其实 Python 的错误提示大多很 “良心”,只要看懂错误类型和原因,90% 的问题都能 10 分钟内解决。
今天就把开发者最常踩的 10 个坑拎出来,从 “基础语法错” 到 “进阶逻辑错”,逐个拆明白:每个错误都给 “能跑出错的代码”“完整错误信息”“底层原因”“修复代码”,最后还加了避坑技巧。不管你是刚学 Python 的新手,还是写了半年的进阶者,掌握这些都能少走弯路,调试效率翻倍!
先上总览:10 大错误清单(从简单到复杂)
先给大家画个重点,这 10 个错误覆盖了 “语法→变量→模块→数据→逻辑” 全流程,后面会逐个深扒:
错误名称 | 难度等级 | 核心原因 | 高频场景 |
---|---|---|---|
1. IndentationError | 入门 | 缩进不一致 / 混用 Tab 和空格 / 缺少缩进 | if/for/def 代码块下的缩进问题 |
2. SyntaxError | 入门 | 代码不符合 Python 语法规则(少括号 / 逗号等) | 括号不配对、语句末尾多冒号、关键字拼写错 |
3. NameError | 入门 | 变量 / 函数没定义就用,或作用域错 | 变量在函数内定义却在外部用、拼写错变量名 |
4. ModuleNotFoundError | 入门 | 没装第三方库,或模块路径不对 | import requests/pandas 却没 pip 安装 |
5. TypeError | 进阶 | 数据类型不匹配(比如字符串和数字相加) | "123"+4、列表调字典的方法(list.get ()) |
6. IndexError | 进阶 | 列表 / 元组索引越界(下标超过最大范围) | 列表长度 3,却访问 index=3(索引从 0 开始) |
7. KeyError | 进阶 | 字典里没有要访问的键 | user={"name":"张三"},却取 user ["age"] |
8. ZeroDivisionError | 进阶 | 除法 / 取模运算中除数为 0 | 10/0、5%0 |
9. ValueError | 进阶 | 类型对但值不符合要求(比如字符串转整数失败) | int ("abc")、列表 remove 不存在的值 |
10. RecursionError | 高阶 | 递归次数超过 Python 默认限制(默认 1000 次) | 递归没写终止条件、终止条件永远不满足 |
错误 1:IndentationError(缩进错误)—— Python 最基础的 “格式坑”
Python 不像其他语言(比如 Java、C++)用大括号 {}
划分代码块,而是靠 “缩进”—— 缩进错了,Python 直接看不懂代码结构,第一个给你报 IndentationError。
踩坑场景 1:同一代码块缩进不一致
# 需求:判断年龄是否成年,成年就打印提示
age = 18
if age >= 18:
print("已成年") # 这里用了 4 个空格缩进
print("可以独立办理业务") # 这里用了 2 个空格缩进(和上面不一致)
运行后错误信息:
File "test.py", line 5
print("可以独立办理业务")
^
IndentationError: inconsistent use of tabs and spaces in indentation
# 翻译:缩进错误:缩进中混用了 Tab 和空格(或缩进长度不一致)
踩坑场景 2:缺少必要的缩进
# 需求:定义函数打印问候语
def say_hello():
print("Hello!") # 函数下的代码块没缩进(Python 要求必须缩进)
运行后错误信息:
File "test.py", line 3
print("Hello!")
^
IndentationError: expected an indented block
# 翻译:缩进错误:需要一个缩进块(这里没缩进)
踩坑场景 3:Tab 和空格混用
很多人写代码时,有时候按 Tab,有时候按空格 ——Python 对缩进很严格,Tab 相当于 8 个空格(不同 IDE 可能设为 4 个,但混用就是错),和手动按的 4 个空格不兼容。
# 坑:第一行用 Tab,第二行用 4 空格
if age > 18:
("成年") # Tab 缩进
print("可以开车") # 4 空格缩进(和 Tab 不兼容) print
运行后错误信息:
File "test.py", line 4
print("可以开车")
^
IndentationError: inconsistent use of tabs and spaces in indentation
错误解析:
Python 规定:同一级别的代码块,缩进必须完全一致—— 要么全用 4 个空格(官方推荐),要么全用 Tab(不推荐,容易和空格混),绝对不能又用 Tab 又用空格,也不能同一代码块缩进长度不一样。
修复方案:
-
统一缩进格式:所有代码块都用 4 个空格(Python 官方推荐);
-
修复场景 1 的代码(统一 4 空格):
age = 18
if age >= 18:
print("已成年") # 4 空格
print("可以独立办理业务") # 4 空格(和上面一致)
- 修复场景 2 的代码(加 4 空格缩进):
def say_hello():
print("Hello!") # 函数下的代码块必须缩进
避坑技巧:
-
用 IDE 自动处理:PyCharm、VS Code 都能设置 “Tab 自动转 4 个空格”(VS Code 搜 “Insert Spaces”,勾选后按 Tab 就是 4 空格);
-
显示隐藏的缩进符号:IDE 开启 “显示空格 / 制表符”(VS Code 按 Ctrl+Shift+P,搜 “Toggle Render Whitespace”),能直观看到是空格还是 Tab;
-
别手贱改缩进:复制别人代码时,先检查缩进,不一致就用 IDE 的 “重新缩进” 功能(PyCharm 右键→Reformat Code)。
错误 2:SyntaxError(语法错误)—— Python “看不懂” 你的代码
SyntaxError 是 “语法错误”,意思是你写的代码不符合 Python 的 “语法规矩”,Python 解析器读不懂,直接罢工。常见原因是 “少括号、少逗号、关键字拼错”。
踩坑场景 1:括号 / 引号不配对
# 坑 1:print 函数少了右括号
print("Hello Python" # 少了 )
# 坑 2:字符串少了右引号
message = "今天天气不错 # 少了 "
# 坑 3:字典少了右大括号
user = {"name": "张三", "age": 20 # 少了 }
运行后错误信息(以少括号为例):
File "test.py", line 2
print("Hello Python"
^
SyntaxError: unexpected EOF while parsing
# 翻译:语法错误:解析时遇到意外的文件结束(没找到配对的括号)
踩坑场景 2:语句末尾多了冒号
# 坑:普通赋值语句末尾加了冒号(只有 if/for/def 等需要冒号)
x = 10: # 多了 :
运行后错误信息:
File "test.py", line 2
x = 10:
^
SyntaxError: invalid syntax
# 翻译:语法错误:无效的语法(冒号加错地方了)
踩坑场景 3:关键字拼写错误
Python 有 35 个关键字(比如 if、for、while、def、class),拼写错了会报 SyntaxError。
# 坑:把 if 拼成了 iff
iff age > 18: # 错把 if 写成 iff
print("成年")
运行后错误信息:
File "test.py", line 2
iff age > 18:
^
SyntaxError: invalid syntax
错误解析:
Python 解析代码时,会按 “语法规则” 逐行检查 —— 比如括号必须成对、冒号只能用在代码块开头、关键字不能拼错。只要违反这些规则,就会直接报 SyntaxError,而且会用 ^
符号指错位置,很好定位。
修复方案:
-
括号 / 引号不配对:按
^
提示的位置,补全缺失的括号 / 引号(比如给 print 加)
); -
多冒号:删掉普通语句末尾的冒号(比如
x = 10
去掉:
); -
关键字拼错:核对 Python 关键字表,修正拼写(比如
iff
改成if
)。
修复后的代码(场景 1 为例):
# 补全括号
print("Hello Python")
# 补全引号
message = "今天天气不错"
# 补全大括号
user = {"name": "张三", "age": 20}
避坑技巧:
-
写代码时 “成对敲”:敲
(
就立刻敲)
,敲"
就立刻敲"
,再把光标移到中间写内容,避免漏补; -
用 IDE 的语法提示:PyCharm、VS Code 会实时标红语法错误,看到红色波浪线就及时改;
-
记不住关键字?不用死记:IDE 会给关键字变色(比如 PyCharm 里 if/for 是蓝色),如果没变色,大概率拼错了。
错误 3:NameError(名称错误)—— 变量 / 函数 “没定义就用”
NameError 是 “名称错误”,核心原因就一个:你用了一个 “没定义过的变量 / 函数”,或者 “定义了但不在当前作用域”(比如函数里定义的变量,在函数外直接用)。
踩坑场景 1:变量没定义就用
# 坑:直接打印 x,但 x 从来没赋值过
print(x) # x 没定义
运行后错误信息:
Traceback (most recent call last):
File "test.py", line 2, in <module>
print(x)
NameError: name 'x' is not defined
# 翻译:名称错误:'x' 这个名称没定义
踩坑场景 2:变量定义在函数内,外部用
# 坑:在函数里定义了 num,却在函数外打印
def calculate():
num = 100 # 函数内的局部变量
print(num) # 函数外访问局部变量,报错
运行后错误信息:
Traceback (most recent call last):
File "test.py", line 5, in <module>
print(num)
NameError: name 'num' is not defined
踩坑场景 3:变量名拼写错误
# 坑:把 total 拼成了 toatl(字母顺序错了)
total = 100 + 200
print(toatl) # 拼写错,Python 认为 toatl 是新变量(没定义)
运行后错误信息:
Traceback (most recent call last):
File "test.py", line 3, in <module>
print(toatl)
NameError: name 'toatl' is not defined
错误解析:
Python 里的变量 / 函数有 “作用域”:
-
全局作用域:在函数外定义的变量,整个文件能用;
-
局部作用域:在函数 / 类里定义的变量,只有函数 / 类内部能用。
如果在某个作用域里用了 “没定义的名称”,或者 “跨作用域访问局部变量”,就会报 NameError。
修复方案:
-
变量没定义:先给变量赋值(定义),再使用;
-
跨作用域访问:要么把局部变量改成全局变量(用
global
关键字),要么让函数返回变量; -
拼写错误:核对变量名,修正拼写(比如
toatl
改成total
)。
修复后的代码(场景 2 为例):
# 方案 1:用 global 把局部变量改成全局变量
def calculate():
global num # 声明 num 是全局变量
num = 100
calculate() # 必须先调用函数,才会定义 num
print(num) # 输出:100
# 方案 2:让函数返回变量(更推荐,避免全局变量污染)
def calculate2():
num = 200
return num # 返回变量
result = calculate2() # 接收函数返回值
print(result) # 输出:200
避坑技巧:
-
变量名 “见名知意”:别用 x、y、a 这种模糊的名字,用 total、user_name 这种,减少拼写错的概率;
-
查作用域:如果报 NameError,先看变量在哪定义的 —— 在函数里就别在外面用,要么返回,要么用 global;
-
用 IDE 自动补全:输入变量名前几个字母,IDE 会提示补全,避免拼写错(比如输 to,IDE 提示 total)。
错误 4:ModuleNotFoundError(模块缺失)—— 要导入的库 “没装” 或 “找不到”
ModuleNotFoundError 是 “模块找不到错误”,最常见的情况是 “想导入第三方库(比如 requests、pandas),但没装”,或者 “自己写的模块路径不对”。
踩坑场景 1:没装第三方库就导入
# 坑:想导入 requests 库发请求,但没装
import requests
response = requests.get("https://www.baidu.com")
print(response.status_code)
运行后错误信息:
Traceback (most recent call last):
File "test.py", line 2, in <module>
import requests
ModuleNotFoundError: No module named 'requests'
# 翻译:模块找不到错误:没有叫 'requests' 的模块
踩坑场景 2:自己写的模块路径不对
假设你有两个文件:
-
main.py
(主文件,想导入 module1 的函数); -
utils/``module1.py
(自己写的模块,里面有add
函数)。
如果直接在 main.py
里导入,会报错:
# main.py 里的代码(坑)
from module1 import add # 没写路径,找不到 module1
result = add(1, 2)
print(result)
运行后错误信息:
Traceback (most recent call last):
File "main.py", line 2, in <module>
from module1 import add
ModuleNotFoundError: No module named 'module1'
错误解析:
Python 导入模块时,会按 “模块搜索路径” 找:
-
先找当前运行脚本所在的文件夹;
-
再找 Python 安装目录下的
site-packages
(第三方库都装在这里); -
找不到就报 ModuleNotFoundError。
所以,要么第三方库没装(不在 site-packages
里),要么自己的模块不在搜索路径里。
修复方案:
-
第三方库没装:用
pip install 库名
安装(比如装 requests 用pip install requests
); -
自己的模块路径不对:要么写全路径(相对路径 / 绝对路径),要么把模块所在文件夹加入搜索路径。
修复后的代码:
- 场景 1(装库后):
# 先执行 pip install requests(终端里跑)
import requests
response = requests.get("https://www.baidu.com")
print(response.status_code) # 输出:200(成功)
- 场景 2(补全路径):
# main.py 里用相对路径导入(utils 是文件夹名)
from utils.module1 import add
result = add(1, 2)
print(result) # 输出:3
避坑技巧:
-
装库时 “对应 Python 环境”:如果用了虚拟环境,要先激活环境再
pip install
;如果报 “装了还找不到”,用python -m pip install 库名
(指定当前 Python 的 pip); -
自己的模块别和标准库重名:别把自己的模块叫
requests.py
或os.py
,会和 Python 标准库冲突,导致导入错模块; -
查模块路径:如果不确定模块在哪,用以下代码看搜索路径:
import sys
print(sys.path) # 打印 Python 模块搜索路径
错误 5:TypeError(类型不匹配)—— “鸡同鸭讲”,类型不对没法操作
TypeError 是 “类型错误”,意思是你对一个数据做了 “它类型不支持的操作”—— 比如用字符串加数字(“123”+4)、给列表调字典的方法(list.get ()),就像 “让鸡游泳、让鸭爬树”,根本做不了。
踩坑场景 1:不同类型的数据做运算
# 坑 1:字符串和数字相加
age = 20
message = "我的年龄是:" + age # "我的年龄是:"是字符串,age是整数
# 坑 2:列表和整数相乘(列表只能和整数相乘,不能和浮点数)
nums = [1, 2, 3]
new_nums = nums * 2.5 # 用了浮点数 2.5
运行后错误信息(场景 1 为例):
Traceback (most recent call last):
File "test.py", line 3, in <module>
message = "我的年龄是:" + age
TypeError: can only concatenate str (not "int") to str
# 翻译:类型错误:只能把字符串和字符串拼接,不能和整数拼
踩坑场景 2:调用不存在的方法(类型不对)
# 坑 1:给列表调字典的 get 方法(列表没有 get 方法)
nums = [1, 2, 3]
nums.get(0) # 错,列表用索引访问,不是 get()
# 坑 2:给整数调 append 方法(整数没有 append 方法)
x = 10
x.append(5) # 错,只有列表/字典等可变对象有 append
运行后错误信息(场景 1 为例):
Traceback (most recent call last):
File "test.py", line 3, in <module>
nums.get(0)
AttributeError: 'list' object has no attribute 'get'
# 注:虽然报错是 AttributeError,但根源是类型错(用列表当字典用)
踩坑场景 3:函数参数类型不对
# 坑:定义函数要整数,却传了字符串
def add(a, b):
return a + b
result = add("1", 2) # 第一个参数是字符串 "1",第二个是整数 2
运行后错误信息:
Traceback (most recent call last):
File "test.py", line 5, in <module>
result = add("1", 2)
File "test.py", line 3, in add
return a + b
TypeError: can only concatenate str (not "int") to str
错误解析:
Python 是 “动态类型语言”—— 变量不用声明类型,但每个变量都有明确的类型(比如 int、str、list),且不同类型支持的操作不同:
-
str 支持拼接(+)、切片,但不支持和 int 相加;
-
list 支持 append、索引访问,但不支持 get () 方法;
-
int 支持加减乘除,但不支持 append ()。
如果违反类型支持的操作,就会报 TypeError。
修复方案:
-
不同类型运算:先转成同一类型(比如用
str()
转整数为字符串,用int()
转字符串为整数); -
调用错误方法:用对应类型的方法(列表用索引
nums[0]
,字典用dict.get()
); -
函数参数类型错:传符合函数要求的类型,或在函数内加类型判断。
修复后的代码:
- 场景 1(字符串 + 整数):
age = 20
# 方案 1:把整数转字符串
message = "我的年龄是:" + str(age)
# 方案 2:用 f-string(自动转类型,更推荐)
message2 = f"我的年龄是:{age}"
print(message2) # 输出:我的年龄是:20
- 场景 3(函数参数):
def add(a, b):
# 加类型判断,提前报错(更健壮)
if not (isinstance(a, int) and isinstance(b, int)):
raise TypeError("a 和 b 必须是整数")
return a + b
result = add(int("1"), 2) # 把字符串转整数
print(result) # 输出:3
避坑技巧:
-
查数据类型:用
type(变量)
看类型(比如type(age)
输出<class 'int'>
); -
函数加类型提示:用 Python 3.5+ 的类型提示,让调用者知道该传什么类型:
# 类型提示:a 和 b 是 int,返回值是 int
def add(a: int, b: int) -> int:
return a + b
- 用
isinstance()
判断类型:在函数内加判断,避免传错类型(比如isinstance(a, int)
检查 a 是否是整数)。
错误 6:IndexError(索引越界)—— 列表 / 元组 “下标超了范围”
IndexError 是 “索引错误”,只发生在列表、元组、字符串这些 “可索引对象” 上 —— 意思是你访问的索引(下标)超过了对象的最大范围(比如列表长度是 3,却访问 index=3,因为索引从 0 开始)。
踩坑场景 1:列表索引超过最大范围
# 坑:列表有 3 个元素(索引 0、1、2),却访问 index=3
fruits = ["苹果", "香蕉", "橙子"]
print(fruits[3]) # 索引 3 不存在
运行后错误信息:
Traceback (most recent call last):
File "test.py", line 3, in <module>
print(fruits[3])
IndexError: list index out of range
# 翻译:索引错误:列表索引超出范围
踩坑场景 2:循环中索引超界
# 坑:用 for 循环遍历索引,条件写多了(i 到 len(fruits),应该是 len(fruits)-1)
fruits = ["苹果", "香蕉", "橙子"]
# len(fruits) 是 3,i 会取 0、1、2、3(3 超界)
for i in range(len(fruits) + 1):
print(fruits[i])
运行后错误信息:
苹果
香蕉
橙子
Traceback (most recent call last):
File "test.py", line 5, in <module>
print(fruits[i])
IndexError: list index out of range
踩坑场景 3:空列表访问索引
# 坑:列表是空的(没有元素),却访问 index=0
empty_list = []
print(empty_list[0]) # 空列表没有任何索引
运行后错误信息:
Traceback (most recent call last):
File "test.py", line 3, in <module>
print(empty_list[0])
IndexError: list index out of range
错误解析:
Python 中可索引对象的索引规则:
-
正向索引:从 0 开始,最大索引是 “长度 - 1”(比如长度 3 的列表,最大索引是 2);
-
反向索引:从 -1 开始(最后一个元素是 -1,倒数第二个是 -2),最小索引是 “- 长度”(比如长度 3 的列表,最小索引是 -3);
-
空列表:没有任何索引(正向、反向都没有),访问任何索引都会报错。
如果访问的索引不在 “0 ~ 长度 - 1” 或 “- 长度~-1” 范围内,就会报 IndexError。
修复方案:
-
索引超界:把索引改成 “0 ~ 长度 - 1” 范围内的值(比如
fruits[3]
改成fruits[2]
); -
循环超界:循环条件用
range(len(fruits))
(别加 +1),或直接遍历元素(不用索引); -
空列表:先判断列表非空,再访问索引(用
if 列表名:
判断)。
修复后的代码:
- 场景 2(循环超界):
fruits = ["苹果", "香蕉", "橙子"]
# 方案 1:用 range(len(fruits))(正确范围)
for i in range(len(fruits)):
print(fruits[i]) # 输出:苹果、香蕉、橙子
# 方案 2:直接遍历元素(更推荐,不用管索引)
for fruit in fruits:
print(fruit) # 输出一样,代码更简洁
- 场景 3(空列表):
empty_list = []
# 先判断列表非空,再访问
if empty_list: # 空列表判断为 False,非空为 True
print(empty_list[0])
else:
print("列表是空的,无法访问索引") # 输出这句话
避坑技巧:
-
查长度:用
len(对象)
看可索引对象的长度(比如len(fruits)
知道最大索引是 len (fruits)-1); -
少用索引遍历:能直接遍历元素就别用索引(比如
for fruit in fruits
比for i in range(len(fruits))
安全); -
反向索引更安全:想访问最后一个元素,用
fruits[-1]
(不用算长度,避免超界)。
错误 7:KeyError(字典键不存在)—— 字典里 “没有你要的键”
KeyError 是 “键错误”,只发生在字典上 —— 意思是你想从字典里取一个 “不存在的键”,比如字典只有 "name" 键,却取 "age" 键。
踩坑场景 1:直接访问不存在的键
# 坑:字典只有 "name" 和 "gender" 键,却取 "age" 键
user = {
"name": "张三",
"gender": "男"
}
print(user["age"]) # "age" 键不存在
运行后错误信息:
Traceback (most recent call last):
File "test.py", line 7, in <module>
print(user["age"])
KeyError: 'age'
# 翻译:键错误:'age' 这个键不存在
踩坑场景 2:循环中访问不存在的键
# 坑:遍历键列表,但有的键不在字典里
user = {"name": "张三", "gender": "男"}
keys_to_check = ["name", "age", "city"] # "age" 和 "city" 不在字典里
for key in keys_to_check:
print(user[key]) # 遇到 "age" 就报错
运行后错误信息:
张三
Traceback (most recent call last):
File "test.py", line 6, in <module>
print(user[key])
KeyError: 'age'
错误解析:
字典是 “键值对” 结构(key: value),访问值必须通过存在的键 ——Python 不会像列表那样 “自动补值”,如果键不在字典的 keys()
里,就会直接报 KeyError。
修复方案:
-
直接访问:用
dict.get(key, 默认值)
替代dict[key]
(没找到键时返回默认值,不报错); -
循环访问:先判断键是否在字典里(用
if key in dict:
),或用get()
方法; -
确保键存在:定义字典时补全需要的键,或在访问前给字典加键。
修复后的代码:
- 场景 1(直接访问):
user = {"name": "张三", "gender": "男"}
# 方案 1:用 get() 方法,没找到返回 "未知"(默认值)
print(user.get("age", "未知")) # 输出:未知
# 方案 2:先判断键是否存在
if "age" in user:
print(user["age"])
else:
print("age 键不存在") # 输出这句话
- 场景 2(循环访问):
user = {"name": "张三", "gender": "男"}
keys_to_check = ["name", "age", "city"]
for key in keys_to_check:
# 用 get() 避免报错,没找到返回 "无"
print(f"{key}: {user.get(key, '无')}")
# 输出:
# name: 张三
# age: 无
# city: 无
避坑技巧:
-
优先用
get()
方法:访问字典时,不确定键是否存在就用get()
,别直接用dict[key]
; -
查所有键:用
dict.keys()
看字典有哪些键(比如print(user.keys())
输出dict_keys(['name', 'gender'])
); -
初始化字典时补默认值:如果某些键可能缺失,定义时就设默认值(比如
user = {"name": "张三", "age": None}
)。
错误 8:ZeroDivisionError(除以零)—— 除法 / 取模 “除数不能为 0”
ZeroDivisionError 是 “除以零错误”,发生在除法(/)、取模(%)、整除(//)运算中 —— 只要除数是 0,不管被除数是什么,都会报错(数学上除数也不能为 0)。
踩坑场景 1:直接用 0 当除数
# 坑 1:除法运算,除数是 0
result1 = 10 / 0
# 坑 2:取模运算,除数是 0
result2 = 5 % 0
# 坑 3:整除运算,除数是 0
result3 = 8 // 0
运行后错误信息(场景 1 为例):
Traceback (most recent call last):
File "test.py", line 2, in <module>
result1 = 10 / 0
ZeroDivisionError: division by zero
# 翻译:除以零错误:用零除
踩坑场景 2:变量除数变成 0(隐性错误)
有时候除数不是直接写 0,而是变量变成了 0,更难发现:
# 坑:计算平均分,学生人数是 0(比如没学生),除数变成 0
total_score = 300 # 总分
student_count = 0 # 学生人数(这里是 0)
average = total_score / student_count # 除数是 0,报错
print(average)
运行后错误信息:
Traceback (most recent call last):
File "test.py", line 5, in <module>
average = total_score / student_count
ZeroDivisionError: division by zero
错误解析:
数学上 “除数不能为 0” 是基本规则,Python 遵循这个规则 —— 任何数(正数、负数、0)除以 0,或对 0 取模 / 整除,都会触发 ZeroDivisionError(因为结果是无穷大,无法用有限数字表示)。
修复方案:
-
直接除数 0:把除数改成非 0 的数(比如
10/0
改成10/2
); -
变量除数 0:在运算前判断除数是否为 0,是就提示错误或处理(比如返回 0 平均分)。
修复后的代码(场景 2 为例):
total_score = 300
student_count = 0
# 运算前判断除数是否为 0
if student_count == 0:
print("学生人数为 0,无法计算平均分")
average = 0 # 设默认值,避免程序崩溃
else:
average = total_score / student_count
print(f"平均分:{average}") # 输出:学生人数为 0,无法计算平均分;平均分:0
避坑技巧:
-
涉及除法先判 0:只要代码里有
/
、%
、//
,先检查除数是否为 0(尤其是变量除数); -
用 try-except 捕获错误:如果不确定除数是否会为 0,用异常捕获避免程序崩溃:
total_score = 300
student_count = 0
try:
average = total_score / student_count
except ZeroDivisionError:
print("除数不能为 0!")
average = 0
print(f"平均分:{average}") # 输出:除数不能为 0!;平均分:0
错误 9:ValueError(值错误)—— “类型对但值不对”,无法处理
ValueError 是 “值错误”,和 TypeError 容易搞混 ——TypeError 是 “类型不对”(比如字符串加整数),ValueError 是 “类型对但值不符合要求”(比如字符串是 "abc",想转成整数,类型是字符串没错,但值不能转)。
踩坑场景 1:类型对但值无法转换
# 坑 1:字符串类型对,但值 "abc" 不能转成整数
num = int("abc") # "abc" 是字符串(类型对),但不能转 int
# 坑 2:字符串是数字,但有空格,也不能转
num2 = int("123 ") # 末尾有空格,值不符合要求
运行后错误信息(场景 1 为例):
Traceback (most recent call last):
File "test.py", line 2, in <module>
num = int("abc")
ValueError: invalid literal for int() with base 10: 'abc'
# 翻译:值错误:'abc' 不是 base 10(十进制)的有效整数字面量
踩坑场景 2:列表移除不存在的值
# 坑:列表有 ["苹果", "香蕉"],却要移除 "橙子"(值不存在)
fruits = ["苹果", "香蕉"]
fruits.remove("橙子") # 类型是列表(对),但值 "橙子" 不在列表里
运行后错误信息:
Traceback (most recent call last):
File "test.py", line 3, in <module>
fruits.remove("橙子")
ValueError: list.remove(x): x not in list
# 翻译:值错误:list.remove(x):x 不在列表里
踩坑场景 3:函数参数值不符合要求
# 坑:求平方根,但值是负数(math.sqrt 不支持负数)
import math
result = math.sqrt(-10) # 类型是整数(对),但值 -10 不符合要求(平方根不能是负)
运行后错误信息:
Traceback (most recent call last):
File "test.py", line 4, in <module>
result = math.sqrt(-10)
ValueError: math domain error
# 翻译:值错误:数学域错误(负数不能开平方)
错误解析:
ValueError 的核心是 “类型正确,但值不在允许的范围内”:
-
int("abc")
:类型是 str(正确),但值 "abc" 不在 “可转成 int 的字符串范围”(范围是 "0~9" 组成的字符串); -
list.remove("橙子")
:类型是 list(正确),但值 "橙子" 不在 “列表的元素范围”; -
math.sqrt(-10)
:类型是 int(正确),但值 -10 不在 “可开平方的数值范围”(范围是 ≥0 的数)。
修复方案:
-
值无法转换:先处理值(比如去掉字符串空格、确保是数字字符串),再转换;
-
移除不存在的值:先判断值在列表里,再 remove,或用 try-except 捕获;
-
参数值不对:传符合要求的值(比如给 sqrt 传 ≥0 的数)。
修复后的代码:
- 场景 1(字符串转整数):
# 方案 1:确保字符串是纯数字
num = int("123") # 正确,输出 123
# 方案 2:先去掉字符串空格
num2 = int("123 ".strip()) # strip() 去掉前后空格,正确转成 123
- 场景 2(列表移除值):
fruits = ["苹果", "香蕉"]
target = "橙子"
# 方案 1:先判断值在列表里
if target in fruits:
fruits.remove(target)
else:
print(f"{target} 不在列表里,无法移除") # 输出这句话
# 方案 2:用 try-except 捕获
try:
fruits.remove(target)
except ValueError:
print(f"{target} 不在列表里,无法移除")
避坑技巧:
-
区分 TypeError 和 ValueError:记一句话 ——“类型错是 TypeError,值错是 ValueError”;
-
转换前检查值:比如字符串转整数前,用
str.isdigit()
检查是否是纯数字("123".isdigit()
返回 True,"abc".isdigit()
返回 False); -
查函数参数要求:用
help(函数名)
看参数值的范围(比如help(math.sqrt)
会告诉你参数必须 ≥0)。
错误 10:RecursionError(递归错误)—— 递归 “陷进无限循环”
RecursionError 是 “递归错误”,发生在递归函数中 ——Python 默认递归深度是 1000 次,如果递归次数超过这个限制,或递归没写终止条件(无限递归),就会报错。
踩坑场景 1:递归没写终止条件(无限递归)
# 坑:求 n 的阶乘,但没写终止条件(n 会一直减,直到超过递归深度)
def factorial(n):
return n * factorial(n - 1) # 没终止,一直调用自己
result = factorial(5) # 会无限递归,直到报错
print(result)
运行后错误信息:
Traceback (most recent call last):
File "test.py", line 5, in <module>
result = factorial(5)
File "test.py", line 3, in factorial
return n * factorial(n - 1)
File "test.py", line 3, in factorial
return n * factorial(n - 1)
... # 中间省略很多行(重复调用)
RecursionError: maximum recursion depth exceeded in comparison
# 翻译:递归错误:比较中超过了最大递归深度
踩坑场景 2:终止条件永远不满足
# 坑:求 n 的阶乘,终止条件是 n == 0,但递归调用时 n 一直减 2(n 是奇数就到不了 0)
def factorial(n):
if n == 0: # 终止条件:n == 0
return 1
return n * factorial(n - 2) # 错:减 2,不是减 1
result = factorial(5) # 5→3→1→-1→-3... 永远到不了 0,报错
print(result)
运行后错误信息:
RecursionError: maximum recursion depth exceeded in comparison
错误解析:
递归函数需要满足两个条件:
-
有 “终止条件”:当满足条件时,停止递归(比如阶乘的
n == 0
时返回 1); -
每次递归 “靠近终止条件”:比如阶乘每次
n-1
,让 n 逐渐接近 0。
如果缺少任何一个条件,递归会无限进行 ——Python 为了防止栈溢出(内存耗尽),设置了默认递归深度(1000 次),超过就报 RecursionError。
修复方案:
-
加终止条件:给递归函数加明确的终止条件(比如阶乘的
n == 0
); -
确保靠近终止条件:每次递归让参数向终止条件靠近(比如阶乘用
n-1
,不是n-2
); -
超过深度改循环:如果递归深度确实需要超过 1000(很少见),用循环替代递归(避免栈溢出)。
修复后的代码(场景 1 为例):
# 正确的阶乘递归函数(有终止条件,且靠近终止条件)
def factorial(n):
# 终止条件:n == 0 或 n == 1(阶乘 0! 和 1! 都是 1)
if n in (0, 1):
return 1
# 每次 n-1,靠近终止条件
return n * factorial(n - 1)
result = factorial(5) # 5*4*3*2*1*1 = 120
print(result) # 输出:120
用循环替代递归(深度大时推荐):
# 用循环求阶乘,避免递归深度问题
def factorial_loop(n):
result = 1
for i in range(1, n+1):
result *= i
return result
result = factorial_loop(1000) # 即使 n=1000,循环也能处理,不会报错
print(result)
避坑技巧:
-
写递归先加终止条件:不管递归逻辑多复杂,先把终止条件写好;
-
测试小值:递归函数先测小值(比如 n=5),看是否能正常终止;
-
别用递归处理大深度:超过 1000 次的递归,改用循环(递归会占栈内存,循环更高效)。
常见问题(FAQ):你可能还会遇到的延伸问题
Q1:VS Code 里怎么设置 “Tab 自动转 4 个空格”?
答:打开 VS Code,按 Ctrl+Shift+P(Windows)/ Command+Shift+P(Mac),输入 “Preferences: Open Settings (JSON)”,在配置文件里加两行:
"editor.tabSize": 4,
"editor.insertSpaces": true
这样按 Tab 就会自动插入 4 个空格,避免缩进错误。
Q2:ModuleNotFoundError 提示 “没这个模块”,但我已经用 pip 装了,怎么办?
答:大概率是 “pip 和当前 Python 环境不匹配”—— 比如你用系统自带的 Python(比如 Python 3.8)装了库,但运行代码用的是虚拟环境的 Python(比如 Python 3.10),两个环境的库不通用。
解决方法:
-
用当前 Python 对应的 pip 安装:终端跑
python -m pip install 库名
(比如python -m pip install requests
),python
是你运行代码时用的命令(比如python3
就用python3 -m pip
); -
激活虚拟环境再装:如果用了虚拟环境,先激活(Windows:
venvScriptsactivate
;Mac/Linux:source venv/bin/activate
),再pip install 库名
。
Q3:怎么快速区分 TypeError 和 ValueError?
答:记两个例子,一看就懂:
-
TypeError:类型不对,比如 “字符串 + 整数”(
"1"+2
)、“列表调 get 方法”([1,2].get(0)
)—— 核心是 “这个类型干不了这个事”; -
ValueError:类型对但值不对,比如 “字符串转整数("abc"→int)”(
int("abc")
)、“负数开平方”(math.sqrt(-10)
)—— 核心是 “这个类型能做,但这个值不行”。
Q4:递归深度超过 1000,能不能临时调大限制?
答:能,但不推荐(容易栈溢出,导致程序崩溃)。调大方法:
import sys
sys.setrecursionlimit(2000) # 把递归深度设为 2000
更推荐的方法是 “用循环替代递归”—— 循环没有深度限制,还更省内存。
面试高频题:这些错误相关的问题要会答
面试题 1:请解释 Python 中 IndentationError 和 SyntaxError 的区别,各举一个例子。
答:两者都是语法相关的错误,但侧重点不同:
- IndentationError 是 “缩进错误”,只和缩进有关 ——Python 靠缩进划分代码块,缩进不一致、混用 Tab 和空格、缺少缩进都会报这个错。比如:
if age > 18:
print("成年")
print("可以开车") # 缩进不一致,报 IndentationError
- SyntaxError 是 “通用语法错误”,涵盖所有不符合 Python 语法的情况 —— 比如括号不配对、关键字拼错、语句末尾多冒号等。比如:
print("Hello" # 括号不配对,报 SyntaxError
面试题 2:遇到 KeyError,你有哪些解决方法?
答:有三种常用方法:
-
用
dict.get(key, 默认值)
方法:没找到键时返回默认值,不报错,比如user.get("age", "未知")
; -
先判断键是否存在:用
if key in dict:
检查,存在再访问,比如:
if "age" in user:
print(user["age"])
else:
print("age 键不存在")
- 用 try-except 捕获错误:不确定键是否存在时,捕获 KeyError 避免程序崩溃,比如:
try:
print(user["age"])
except KeyError:
print("age 键不存在")
实际开发中优先用 get()
方法,代码最简洁。
面试题 3:递归函数为什么会报 RecursionError?怎么解决?
答:报 RecursionError 的原因有两个:
-
递归没有终止条件(无限递归),比如求阶乘没写
n==0
的终止条件; -
递归次数超过 Python 默认深度限制(默认 1000 次),即使有终止条件,次数超了也会报错。
解决方法:
-
给递归加明确的终止条件,且确保每次递归靠近终止条件(比如阶乘用
n-1
); -
递归深度大时,改用循环替代递归(比如用 for 循环求阶乘),避免栈溢出;
-
特殊情况可临时调大递归深度(
sys.setrecursionlimit(2000)
),但不推荐(容易内存溢出)。
面试题 4:请说一个你在项目中遇到过的 Python 错误,怎么排查和解决的?
答:(举实际例子,比如 ModuleNotFoundError)
之前做爬虫项目时,导入 requests 库报 ModuleNotFoundError,排查步骤:
-
先检查是否装了库:终端跑
pip list | grep requests
,发现没装,所以第一步是pip install requests
; -
装完后还是报错,想到可能是环境不匹配 —— 我用的是虚拟环境,但装库时没激活,用的是系统 Python 的 pip;
-
激活虚拟环境(
source venv/bin/activate
),重新pip install requests
,再运行代码就好了。核心是 “先看错误信息定位原因(没装库),再排查环境问题(是否激活虚拟环境)”。
总结:报错不可怕,学会 “读错误信息” 是关键
Python 的错误不可怕,反而很 “友好”—— 错误信息会明确告诉你:
-
错在哪一行(
File "``test.py``", line 3
); -
是什么错误(
NameError
/TypeError
等); -
为什么错(
name 'x' is not defined
/division by zero
等)。
所以遇到报错,别慌,按以下步骤来:
-
看错误信息最后一行:找到错误类型和原因(比如最后一行是
KeyError: 'age'
,就知道是字典没这个键); -
看错误行号:找到代码里对应的行(比如
line 5
,就去第 5 行看); -
结合错误类型分析:比如是 IndentationError 就查缩进,是 ModuleNotFoundError 就查库是否装了;
-
修复后测试:改完代码后跑一次,确认报错消失。
掌握这 10 个常见错误的解析和修复方法,再学会读错误信息,你就能告别 “报错恐慌”,调试效率至少提升一半。记住:编程就是不断踩坑、填坑的过程,每解决一个错误,你的代码能力就会涨一分!