在Python开发这条路上,我见过太多新手和老手踩同样的坑。有些错误看似简单,却能让开发者浪费数小时调试;有些问题隐藏很深,直到项目上线才突然爆发。今天我要分享的这些"坑",都是我在8年Python开发生涯中亲自踩过、并且看到无数同行反复踩过的典型问题。
为什么这些错误如此常见?因为Python作为一门"简单易学"的语言,其灵活性背后隐藏着许多陷阱。动态类型、解释执行、丰富的语法糖,这些特性在提高开发效率的同时,也带来了独特的调试挑战。本文将聚焦那些看似简单却容易出错的场景,提供经过实战检验的解决方案。
python复制count = 0
def increment():
count += 1 # UnboundLocalError
increment()
这个错误让很多从其他语言转来的开发者困惑不已。在Python中,如果在函数内对变量赋值,Python会默认将其视为局部变量,即使外部有同名全局变量。
正确的做法是显式声明全局变量:
python复制count = 0
def increment():
global count
count += 1
increment()
关键点:在函数内只要存在对变量的赋值操作(包括+=等复合操作),Python就会将其视为局部变量,除非显式声明为global。
对于嵌套函数,需要使用nonlocal关键字:
python复制def outer():
x = 10
def inner():
nonlocal x
x += 1
inner()
print(x) # 输出11
python复制def append_to(element, target=[]):
target.append(element)
return target
多次调用这个函数,默认参数会保留之前的值:
python复制print(append_to(1)) # [1]
print(append_to(2)) # [1, 2]
Python的函数默认参数在函数定义时就被求值并存储,而不是每次调用时重新创建。对于可变对象(如列表、字典),这会导致意外的行为。
使用None作为默认值,在函数内部初始化:
python复制def append_to(element, target=None):
if target is None:
target = []
target.append(element)
return target
python复制original = [[1, 2], [3, 4]]
new = original.copy()
new[0][0] = 100
print(original) # [[100, 2], [3, 4]]
对于嵌套结构,需要使用copy模块的deepcopy:
python复制from copy import deepcopy
original = [[1, 2], [3, 4]]
new = deepcopy(original)
new[0][0] = 100
print(original) # [[1, 2], [3, 4]]
深拷贝会递归复制所有对象,对于大型数据结构可能影响性能。在不需要完全隔离的情况下,可以考虑:
python复制numbers = {1, 2, 3, 4}
for num in numbers:
if num % 2 == 0:
numbers.remove(num) # RuntimeError
方案1:创建副本进行迭代
python复制for num in list(numbers):
if num % 2 == 0:
numbers.remove(num)
方案2:使用集合推导式
python复制numbers = {num for num in numbers if num % 2 != 0}
对于字典,直接修改值是可以的,但修改键会导致问题:
python复制d = {'a': 1, 'b': 2}
for k in d:
d[k] += 1 # 安全
# del d[k] # 不安全
安全修改字典键的方法:
python复制d = {'a': 1, 'b': 2}
for k in list(d.keys()):
if some_condition(k):
d[new_key] = d.pop(k)
== 比较值is 比较对象标识(内存地址)python复制a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True
print(a is b) # False
Python对小整数(-5到256)有缓存优化:
python复制a = 256
b = 256
print(a is b) # True
a = 257
b = 257
print(a is b) # False (在交互式环境中)
==is(因为None是单例)python复制try:
risky_operation()
except:
pass # 吞噬所有异常
python复制try:
risky_operation()
except SpecificError as e:
logger.error(f"Operation failed: {e}")
raise # 重新抛出
except (TypeError, ValueError) as e:
handle_known_errors(e)
except Exception as e:
logger.exception("Unexpected error")
raise CustomError("Operation failed") from e
python复制resource = acquire()
try:
use(resource)
finally:
release(resource) # 确保资源释放
python复制functions = []
for i in range(3):
functions.append(lambda: i)
print([f() for f in functions]) # [2, 2, 2] 不是预期的[0,1,2]
Python的闭包是延迟绑定的,循环结束后i的值是2,所有lambda都引用这个最终值。
方案1:使用默认参数立即绑定
python复制functions = []
for i in range(3):
functions.append(lambda i=i: i)
方案2:使用functools.partial
python复制from functools import partial
functions = []
for i in range(3):
functions.append(partial(lambda x: x, i))
python复制f = open('file.txt')
data = f.read()
# 如果中间发生异常,文件可能不会关闭
使用with语句确保资源释放:
python复制with open('file.txt') as f:
data = f.read()
python复制with open('input.txt') as fin, open('output.txt', 'w') as fout:
for line in fin:
fout.write(process(line))
code复制# module_a.py
from module_b import func_b
def func_a():
func_b()
# module_b.py
from module_a import func_a
def func_b():
func_a()
python复制# module_b.py
def func_b():
from module_a import func_a
func_a()
低效做法:
python复制s = ''
for substring in list_of_strings:
s += substring
高效做法:
python复制s = ''.join(list_of_strings)
低效:
python复制new_list = []
for item in iterable:
new_list.append(fn(item))
高效:
python复制new_list = [fn(item) for item in iterable]
python复制# 每次循环都计算len(my_list)
for i in range(len(my_list)):
...
# 预先计算
length = len(my_list)
for i in range(length):
...
python复制import pdb; pdb.set_trace() # 经典断点
# Python 3.7+ 更简洁的写法
breakpoint()
python复制import logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
logger.info('This is an info message')
python复制from typing import List, Dict
def process(items: List[str]) -> Dict[str, int]:
"""处理字符串列表,返回统计字典"""
return {s: len(s) for s in items}
使用mypy进行静态检查:
bash复制pip install mypy
mypy your_script.py
在实际项目中,我发现建立代码审查机制能显著减少这类常见错误。团队成员互相检查代码时,往往能发现作者自己忽略的问题。另外,编写详尽的单元测试不仅能捕获现有错误,还能预防未来可能引入的问题。