💣 23. 易错点与坑点汇总
本章概述
收集 Python 开发中最容易踩的坑,避免重蹈覆辙。 预计学习时间:30 分钟
1. 基础语法
1.1 缩进错误
常见错误
Python 用缩进表示代码块,缩进不一致会报错。
# ❌ 错误:缩进不一致
if True:
print("hello")
print("world") # IndentationError
# ✅ 正确:统一缩进
if True:
print("hello")
print("world")建议
- 用 4 个空格缩进(不要用 Tab)
- 同一个代码块缩进要一致
- 编辑器设置 Tab 自动转空格
1.2 赋值号 vs 等号
常见错误
=是赋值,==是比较,别搞混。
# ❌ 错误:用了赋值号
x = 10
if x = 5: # SyntaxError
print("x等于5")
# ✅ 正确:用等号
if x == 5:
print("x等于5")1.3 中文符号
常见错误
不小心用了中文的括号、逗号、冒号等。
# ❌ 错误:中文符号
print("hello") # 中文括号
# ✅ 正确:英文符号
print("hello") # 英文括号排查技巧
报语法错误又看不出问题时,检查是不是用了中文符号。
2. 数据类型
2.1 整数除法
常见错误
Python 3 中
/是浮点数除法,//才是整数除法。
print(10 / 3) # 3.333...(浮点数)
print(10 // 3) # 3(整数,向下取整)注意负数
//是向下取整,不是截断:print(10 // 3) # 3 print(-10 // 3) # -4(不是-3!)
2.2 浮点数精度问题
常见错误
浮点数运算有精度误差,不要直接比较。
print(0.1 + 0.2) # 0.30000000000000004(不是0.3!)
# ❌ 错误:直接比较
if 0.1 + 0.2 == 0.3:
print("相等") # 不会执行!
# ✅ 正确:比较差值
if abs(0.1 + 0.2 - 0.3) < 1e-9:
print("相等")2.3 字符串和数字拼接
常见错误
字符串不能直接和数字拼接。
age = 18
# ❌ 错误:类型不匹配
print("我今年" + age + "岁") # TypeError
# ✅ 正确1:转字符串
print("我今年" + str(age) + "岁")
# ✅ 正确2:f-string(推荐)
print(f"我今年{age}岁")2.4 可变对象的默认参数
经典坑
默认参数不要用可变对象(列表、字典等)。
# ❌ 错误:默认参数用了可变对象
def add_item(item, lst=[]):
lst.append(item)
return lst
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2](不是[2]!)
print(add_item(3)) # [1, 2, 3](默认列表被复用了)
# ✅ 正确:用None当默认值
def add_item(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
print(add_item(1)) # [1]
print(add_item(2)) # [2](正确)记住
默认参数只在函数定义时计算一次,不是每次调用都重新创建。
3. 列表
3.1 列表是引用类型
常见错误
列表赋值只是引用,不是复制。
a = [1, 2, 3]
b = a # 只是引用,不是复制
b.append(4)
print(a) # [1, 2, 3, 4](a也变了!)
print(b) # [1, 2, 3, 4]# ✅ 正确:复制列表
a = [1, 2, 3]
# 方法1:copy方法
b = a.copy()
# 方法2:切片
b = a[:]
# 方法3:list()
b = list(a)
# 方法4:深拷贝(嵌套列表用)
import copy
b = copy.deepcopy(a)3.2 遍历列表时删除元素
经典坑
遍历列表时删除元素,会导致索引错乱。
# ❌ 错误:遍历时删除
lst = [1, 2, 3, 4, 5]
for item in lst:
if item % 2 == 0:
lst.remove(item)
print(lst) # [1, 3, 5]?不对,实际是 [1, 3, 4, 5]!
# 原因:删除元素后,后面的元素往前移,跳过了一些元素# ✅ 正确1:倒序遍历
lst = [1, 2, 3, 4, 5]
for i in range(len(lst)-1, -1, -1):
if lst[i] % 2 == 0:
del lst[i]
print(lst) # [1, 3, 5]
# ✅ 正确2:列表推导式(推荐)
lst = [1, 2, 3, 4, 5]
lst = [x for x in lst if x % 2 != 0]
print(lst) # [1, 3, 5]3.3 列表乘法的坑
常见错误
用
*创建嵌套列表,会引用同一个列表。
# ❌ 错误:嵌套列表用乘法
matrix = [[0] * 3] * 3
print(matrix) # [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
matrix[0][0] = 1
print(matrix)
# [[1, 0, 0], [1, 0, 0], [1, 0, 0]](三行都变了!)
# 因为三行引用的是同一个列表# ✅ 正确:用列表推导式
matrix = [[0] * 3 for _ in range(3)]
matrix[0][0] = 1
print(matrix)
# [[1, 0, 0], [0, 0, 0], [0, 0, 0]](只有第一行变了)4. 字典
4.1 访问不存在的key
常见错误
用
d[key]访问不存在的 key 会报错。
d = {"name": "Tom", "age": 18}
# ❌ 错误:key不存在会报错
print(d["gender"]) # KeyError
# ✅ 正确1:用get方法(推荐)
print(d.get("gender")) # None
print(d.get("gender", "男")) # 男(默认值)
# ✅ 正确2:先判断
if "gender" in d:
print(d["gender"])4.2 遍历字典的方式
正确姿势
遍历字典用
items()同时拿 key 和 value。
d = {"a": 1, "b": 2, "c": 3}
# ❌ 不推荐:遍历key再取值(多一次查找)
for key in d:
print(key, d[key])
# ✅ 推荐:用items()
for key, value in d.items():
print(key, value)5. 循环
5.1 for 循环变量泄露
Python 特点
for 循环的变量在循环结束后还存在。
for i in range(5):
pass
print(i) # 4(循环变量还在!)注意
这不是 bug,是 Python 的设计。 如果不想变量泄露,可以用函数封装。
5.2 range 的结束值
常见错误
range不包含结束值。
# range(5) 是 0,1,2,3,4(不包含5)
for i in range(5):
print(i) # 0 1 2 3 4
# 要包含5,用range(1, 6)
for i in range(1, 6):
print(i) # 1 2 3 4 56. 函数
6.1 函数参数传递
Python 的参数传递
Python 是「传对象引用」,不是传值也不是传引用。
# 不可变对象:函数内修改不影响外部
def func(x):
x = 100
a = 10
func(a)
print(a) # 10(没变)
# 可变对象:函数内修改会影响外部
def func(lst):
lst.append(100)
a = [1, 2, 3]
func(a)
print(a) # [1, 2, 3, 100](变了)6.2 return 位置
常见错误
return 放错位置,导致循环提前结束。
# ❌ 错误:return在循环里面
def find_first_even(lst):
for num in lst:
if num % 2 == 0:
return num
else:
return None # 不对!第一个不是偶数就返回None了
# ✅ 正确:return在循环外面
def find_first_even(lst):
for num in lst:
if num % 2 == 0:
return num
return None # 遍历完都没找到才返回7. 文件操作
7.1 忘记关闭文件
常见错误
打开文件后忘记关闭,可能导致资源泄漏。
# ❌ 不推荐:手动关闭(中间出错就关不了了)
f = open("file.txt", "r")
content = f.read()
# 如果这里出错了,f.close()就不会执行
f.close()
# ✅ 推荐:用with语句(自动关闭)
with open("file.txt", "r") as f:
content = f.read()
# 离开with块自动关闭,即使出错也会关7.2 编码问题
Windows 常见坑
Windows 默认编码是 GBK,打开 UTF-8 文件会乱码。
# ❌ Windows下可能乱码
with open("file.txt", "r") as f:
content = f.read()
# ✅ 指定编码
with open("file.txt", "r", encoding="utf-8") as f:
content = f.read()好习惯
打开文本文件都加上
encoding="utf-8"。
8. 异常处理
8.1 裸 except
危险写法
不要用裸
except:,会捕获所有异常,包括 KeyboardInterrupt 等。
# ❌ 危险:捕获所有异常
try:
# 你的代码
...
except:
print("出错了")
# 连Ctrl+C都捕获了,想停都停不了
# ✅ 推荐:只捕获你能处理的异常
try:
# 你的代码
...
except ValueError:
print("值错误")
except FileNotFoundError:
print("文件不存在")8.2 异常吞噬
常见错误
捕获了异常但什么都不做,隐藏了问题。
# ❌ 不好:异常被吞了,不知道发生了什么
try:
result = 10 / 0
except:
pass
# ✅ 好:至少打印错误信息
try:
result = 10 / 0
except Exception as e:
print(f"出错了:{e}")9. 面向对象
9.1 忘记 self
常见错误
实例方法的第一个参数必须是
self。
# ❌ 错误:没有self
class Person:
def say_hello(): # 缺少self
print("Hello")
p = Person()
p.say_hello() # TypeError
# ✅ 正确:有self
class Person:
def say_hello(self):
print("Hello")9.2 可变类属性
经典坑
类属性是可变对象时,所有实例共享。
# ❌ 错误:类属性用了可变对象
class Person:
hobbies = [] # 类属性
def add_hobby(self, hobby):
self.hobbies.append(hobby)
p1 = Person()
p2 = Person()
p1.add_hobby("读书")
print(p2.hobbies) # ['读书'](p2的也变了!共享同一个列表)# ✅ 正确:用实例属性
class Person:
def __init__(self):
self.hobbies = [] # 实例属性,每个实例都有自己的
def add_hobby(self, hobby):
self.hobbies.append(hobby)
p1 = Person()
p2 = Person()
p1.add_hobby("读书")
print(p1.hobbies) # ['读书']
print(p2.hobbies) # [](p2的没变)10. 其他常见坑
10.1 is vs ==
常见错误
is比较的是内存地址,==比较的是值。
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True(值相等)
print(a is b) # False(不是同一个对象)小整数缓存
Python 对小整数(-5 到 256)有缓存,所以:
a = 100 b = 100 print(a is b) # True(因为小整数被缓存了) a = 1000 b = 1000 print(a is b) # False(大整数不缓存)不要依赖这个特性,比较值就用
==!
10.2 布尔值判断
Pythonic 写法
直接用对象做布尔判断,不要和 True/False/None 比较。
x = []
# ❌ 不推荐
if x == []:
print("空列表")
# ✅ 推荐
if not x:
print("空列表")None 的判断
判断 None 要用
is None,不要用== None:# ✅ 正确 if x is None: print("x是None") if x is not None: print("x不是None")
10.3 全局变量修改
常见错误
函数内修改全局变量需要用
global声明。
count = 0
# ❌ 错误:直接修改会报错
def increment():
count += 1 # UnboundLocalError
# ✅ 正确:用global声明
def increment():
global count
count += 1建议
尽量少用全局变量,通过参数和返回值传递数据。
10.4 导入循环
常见错误
A 导入 B,B 又导入 A,导致循环导入。
# a.py
from b import func_b
def func_a():
print("func_a")
# b.py
from a import func_a # 循环导入!
def func_b():
print("func_b")解决方法
- 重新组织代码,把共用的部分抽出来
- 在函数内部导入(延迟导入)
- 重构模块结构
🔗 相关章节
- 上一章:常用代码片段100+
- 下一章:实战小项目
- 基础语法:基础语法
- 列表:列表详解
- 字典:字典详解
📝 我的笔记
在这里记录你踩过的坑
# 你踩过的坑
✅ 本章检查清单
- 了解默认参数的坑
- 知道列表是引用类型
- 了解遍历列表时删除元素的问题
- 知道 is 和 == 的区别
- 了解浮点数精度问题
- 知道字典 key 不存在的处理方式
- 了解文件编码问题
- 知道不要用裸 except
- 了解类属性和实例属性的区别
- 知道全局变量修改需要 global