1. Python新手最容易踩的10个坑
作为一名从Python 2.7时代就开始写爬虫的老鸟,我见过太多初学者在相同的地方反复跌倒。今天我就把那些年让我熬夜debug的经典错误整理出来,这些坑可能不会报错,但会导致程序行为异常,甚至埋下严重的安全隐患。
1.1 可变对象作为默认参数
这个坑我至少见过20个同事踩过。当你这样定义函数时:
python复制def add_item(item, items=[]):
items.append(item)
return items
第一次调用add_item(1)返回[1],第二次调用add_item(2)却返回[1,2]。这是因为默认参数在函数定义时就被创建,而不是每次调用时新建。正确的做法是:
python复制def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
实际项目中我曾用这个特性实现了缓存功能,但99%的场景你需要的是上面这种写法。
1.2 循环中修改迭代对象
新手常犯的错误是在遍历列表时删除元素:
python复制names = ['Alice', 'Bob', 'Charlie']
for name in names:
if name.startswith('B'):
names.remove(name) # 危险操作!
这会导致跳过某些元素。安全做法是创建新列表或使用列表推导式:
python复制names = [name for name in names if not name.startswith('B')]
我在处理爬虫结果去重时,就曾因为这个问题漏掉了重要数据。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 变量作用域引发的"灵异事件"
2.1 全局变量与局部变量混淆
python复制count = 0
def increment():
count += 1 # UnboundLocalError!
需要在函数内声明global count。更Pythonic的做法是使用类或闭包。
2.2 闭包变量绑定问题
python复制funcs = []
for i in range(3):
funcs.append(lambda: print(i))
for f in funcs:
f() # 全输出2!
这是因为闭包绑定的是变量i本身。解决方法:
python复制funcs.append(lambda i=i: print(i)) # 立即绑定当前值
3. 字符串处理的那些坑
3.1 编码问题
python复制text = "中文"
with open('file.txt', 'w') as f:
f.write(text) # 可能报UnicodeEncodeError
始终明确指定编码:
python复制with open('file.txt', 'w', encoding='utf-8') as f:
f.write(text)
3.2 字符串拼接性能
用+拼接大量字符串会导致性能问题。应该:
python复制parts = []
for i in range(10000):
parts.append(str(i))
result = ''.join(parts)
4. 文件操作的注意事项
4.1 忘记关闭文件
python复制f = open('file.txt')
# 如果中间抛出异常,文件不会关闭
使用with语句确保文件关闭:
python复制with open('file.txt') as f:
data = f.read()
4.2 文件路径问题
硬编码路径会导致跨平台问题:
python复制path = 'C:\\Users\\name\\file.txt' # Windows专用
应该使用os.path:
python复制import os
path = os.path.join('data', 'file.txt')
5. 导入系统的陷阱
5.1 循环导入
A.py:
python复制from B import b_func
B.py:
python复制from A import a_func
会导致导入失败。解决方法是将公共代码提取到第三个模块。
5.2 相对导入问题
在包内使用相对导入时,直接运行模块会报错:
python复制from .submodule import func # 直接运行会报错
应该通过包名导入或设置PYTHONPATH。
6. 异常处理的正确姿势
6.1 捕获过于宽泛的异常
python复制try:
# 各种操作
except: # 捕获所有异常,包括KeyboardInterrupt
pass
应该明确指定要捕获的异常类型:
python复制try:
# 操作
except (ValueError, TypeError) as e:
logger.error(f"输入错误: {e}")
6.2 异常信息丢失
python复制try:
int('a')
except ValueError:
raise RuntimeError("转换失败") # 原始异常信息丢失
使用raise from保留原始异常:
python复制try:
int('a')
except ValueError as e:
raise RuntimeError("转换失败") from e
7. 性能优化的误区
7.1 过早优化
在没有性能问题时进行优化是浪费时间的常见原因。应该:
- 先写出可读性好的代码
- 用profile找出真正的瓶颈
- 只优化热点代码
7.2 错误使用数据结构
频繁查找时使用列表而不是集合:
python复制names = ['Alice', 'Bob', 'Charlie']
if 'Bob' in names: # O(n)操作
pass
应该:
python复制names = {'Alice', 'Bob', 'Charlie'} # O(1)查找
8. 并发编程的坑
8.1 GIL的误解
以为多线程能提高CPU密集型任务速度:
python复制from threading import Thread
def compute():
# CPU密集型计算
pass
threads = [Thread(target=compute) for _ in range(4)]
[t.start() for t in threads] # 不会真正并行执行
应该用多进程:
python复制from multiprocessing import Process
8.2 共享状态问题
多线程不加锁修改共享变量:
python复制counter = 0
def increment():
global counter
counter += 1
应该使用threading.Lock或multiprocessing.Value。
9. 依赖管理的混乱
9.1 不使用虚拟环境
直接在系统Python中安装包会导致版本冲突。应该:
bash复制python -m venv venv
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
pip install package
9.2 不固定依赖版本
requirements.txt中不指定版本:
code复制requests
numpy
应该:
code复制requests==2.28.1
numpy==1.23.3
更好的做法是使用pipenv或poetry。
10. 调试技巧与工具
10.1 只使用print调试
虽然print简单,但对于复杂问题效率低下。应该:
- 使用
pdb:python复制import pdb; pdb.set_trace() - 使用IDE的调试器
- 使用
logging模块记录运行信息
10.2 忽略警告信息
Python的警告往往预示着未来的错误:
python复制import warnings
warnings.filterwarnings('error') # 将警告转为异常
我在实际项目中最深刻的教训是:看似无害的警告往往会导致线上事故。比如DeprecationWarning提示的API在下个版本真的就被移除了。
