💣 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 5

6. 函数

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")

解决方法

  1. 重新组织代码,把共用的部分抽出来
  2. 在函数内部导入(延迟导入)
  3. 重构模块结构

🔗 相关章节


📝 我的笔记

在这里记录你踩过的坑

# 你踩过的坑
 

✅ 本章检查清单

  • 了解默认参数的坑
  • 知道列表是引用类型
  • 了解遍历列表时删除元素的问题
  • 知道 is 和 == 的区别
  • 了解浮点数精度问题
  • 知道字典 key 不存在的处理方式
  • 了解文件编码问题
  • 知道不要用裸 except
  • 了解类属性和实例属性的区别
  • 知道全局变量修改需要 global